Commit 3e267a81 authored by hex's avatar hex

Optimized GameTextureManager

parent a992169d
Pipeline #39190 failed
...@@ -367,7 +367,13 @@ public class Program : MonoBehaviour ...@@ -367,7 +367,13 @@ public class Program : MonoBehaviour
InterString.initialize("config/translation.conf"); InterString.initialize("config/translation.conf");
// 显示一个简单的加载提示 // 显示一个简单的加载提示
PrintToChat(InterString.Get("正在加载本地数据...")); PrintToChat(InterString.Get("正在加载本地数据..."));
// GameTextureManager.initialize();
if (!GameTextureManager.IsInitialized)
{
GameTextureManager.InitializeAssets(); // 1. 同步加载核心资源
GameTextureManager.StartBackgroundThread(); // 2. 启动后台图片加载线程
}
Config.initialize("config/config.conf"); Config.initialize("config/config.conf");
// [新增] 封装文件加载逻辑,避免重复检查 // [新增] 封装文件加载逻辑,避免重复检查
...@@ -1205,6 +1211,39 @@ public class Program : MonoBehaviour ...@@ -1205,6 +1211,39 @@ public class Program : MonoBehaviour
Resources.UnloadUnusedAssets(); Resources.UnloadUnusedAssets();
onRESIZED(); 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(); fixALLcamerasPreFrame();
wheelValue = UICamera.GetAxis("Mouse ScrollWheel") * 50; wheelValue = UICamera.GetAxis("Mouse ScrollWheel") * 50;
pointedGameObject = null; pointedGameObject = null;
...@@ -1269,6 +1308,15 @@ public class Program : MonoBehaviour ...@@ -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() private void onRESIZED()
{ {
preWid = Screen.width; preWid = Screen.width;
...@@ -1330,6 +1378,10 @@ public class Program : MonoBehaviour ...@@ -1330,6 +1378,10 @@ public class Program : MonoBehaviour
{ {
servants[i].OnQuit(); servants[i].OnQuit();
} }
// 在应用退出时,优雅地关闭 GameTextureManager 的后台线程
GameTextureManager.Shutdown();
Running = false; Running = false;
try try
{ {
......
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using UnityEngine; using UnityEngine;
using YGOSharp.OCGWrapper.Enums; using YGOSharp.OCGWrapper.Enums;
public enum GameTextureType public enum GameTextureType
{ {
card_picture = 0, card_picture = 0,
...@@ -17,153 +16,87 @@ public enum GameTextureType ...@@ -17,153 +16,87 @@ public enum GameTextureType
public class GameTextureManager public class GameTextureManager
{ {
#region --- 线程安全与主线程桥接 ---
// 真正的线程锁,用于保护所有共享的集合
private static readonly object _lock = new object();
// 待加载队列: 主线程请求 -> 背景线程处理
private static readonly Stack<PictureResource> _requestStack = new Stack<PictureResource>();
// 待应用队列: 背景线程处理完毕 -> 主线程创建Texture2D
private static readonly Queue<PictureResource> _mainThreadApplyQueue = new Queue<PictureResource>();
// 已加载资源缓存
private static readonly Dictionary<ulong, PictureResource> _loadedCache = new Dictionary<ulong, PictureResource>();
// 防止重复请求的字典
private static readonly HashSet<ulong> _requestedSet = new HashSet<ulong>();
// 下载请求队列
private static readonly Queue<DownloadRequest> _downloadRequestQueue = new Queue<DownloadRequest>();
#region Main-Thread Bridge & Initialization // 背景I/O线程
// ADDED: A structure to hold download request details. private static Thread _ioThread;
internal struct DownloadRequest // 'internal' is good practice for helper structs private static bool _isRunning = false;
public static bool IsInitialized = false;
// 内部结构体,用于传递下载任务
internal struct DownloadRequest
{ {
public string Url; public string Url;
public string FilePath; public string FilePath;
public PictureResource PicResource; public PictureResource PicResource;
} }
// ADDED: A thread-safe queue for download requests from the background thread. // 主线程Runner的交互接口
private static readonly Queue<DownloadRequest> downloadRequestQueue = new Queue<DownloadRequest>(); internal static bool HasMainThreadTasks()
// 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) lock (_lock)
{ {
return downloadRequestQueue.Dequeue(); return _mainThreadApplyQueue.Count > 0;
} }
} }
#endregion
static bool bLock = false; internal static PictureResource GetNextMainThreadTask()
static Stack<PictureResource> waitLoadStack = new Stack<PictureResource>();
static Dictionary<ulong, PictureResource> loadedList = new Dictionary<ulong, PictureResource>();
static Dictionary<ulong, bool> addedMap = new Dictionary<ulong, bool>();
static readonly HttpDldFile df = new HttpDldFile();
public class BitmapHelper
{ {
public System.Drawing.Color[,] colors = null; lock (_lock)
public BitmapHelper(string path)
{ {
Bitmap bitmap; return _mainThreadApplyQueue.Count > 0 ? _mainThreadApplyQueue.Dequeue() : null;
try
{
bitmap = (Bitmap)Image.FromFile(path);
}
catch (Exception)
{
bitmap = new Bitmap(10, 10);
for (int i = 0; i < 10; i++)
{
for (int w = 0; w < 10; w++)
{
bitmap.SetPixel(i, w, System.Drawing.Color.White);
}
}
}
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 bool HasDownloadRequests()
{
lock (_lock)
{ {
return colors[a, b]; return _downloadRequestQueue.Count > 0;
} }
} }
public static void clearUnloaded() internal static DownloadRequest GetNextDownloadRequest()
{ {
while (true) lock (_lock)
{ {
try return _downloadRequestQueue.Count > 0 ? _downloadRequestQueue.Dequeue() : default(DownloadRequest);
{
while (waitLoadStack.Count > 0)
{
var a = waitLoadStack.Pop();
addedMap.Remove((UInt64)a.type << 32 | (UInt64)a.code);
}
break;
}
catch (Exception e)
{
Thread.Sleep(10);
Debug.Log(e);
}
} }
} }
public static void clearAll() public static void Shutdown()
{ {
while (true) _isRunning = false;
if (_ioThread != null && _ioThread.IsAlive)
{ {
try // 给线程一个正常退出的机会
{ _ioThread.Join(500);
waitLoadStack.Clear();
loadedList.Clear();
addedMap.Clear();
break;
}
catch (Exception e)
{
Thread.Sleep(10);
Debug.Log(e);
}
} }
} }
#endregion
#region --- 资源数据结构 ---
public class PictureResource public class PictureResource
{ {
public GameTextureType type; public GameTextureType type;
...@@ -171,928 +104,569 @@ public class GameTextureManager ...@@ -171,928 +104,569 @@ public class GameTextureManager
public bool pCard = false; public bool pCard = false;
public float k = 1; public float k = 1;
//public bool autoMade = false; // --- 数据流 ---
public byte[] data = null; // 1. [背景线程] 从文件读取的原始字节
public float[,,] hashed_data = null; public byte[] rawData = null;
public Texture2D u_data = null; // 2. [主线程] 从rawData转换来的像素数据,用于处理
public Texture2D nullReturen = null; 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) public PictureResource(GameTextureType t, long c, Texture2D n)
{ {
type = t; type = t;
code = c; 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>(); #endregion
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;
public static Texture2D rs = null; #region --- 核心公共接口 (API) ---
public static Texture2D ts = null; // 对外接口保持不变
public static Texture2D get(long code, GameTextureType type, Texture2D nullReturnValue = null)
internal static IEnumerator DownloadAndProcessFile(DownloadRequest request)
{ {
yield return UnityFileDownloader.DownloadFileAsync(request.Url, request.FilePath, (success) => UInt64 key = hashPic(code, type);
lock (_lock)
{ {
if (success) PictureResource resource;
if (_loadedCache.TryGetValue(key, out resource))
{ {
Debug.Log("Download successful, processing card picture: " + request.PicResource.code); // 如果已经有最终纹理,直接返回
LoadCardPicture(request.PicResource, request.FilePath); if (resource.finalTexture != null)
{
// myBack作为未找到图片时的替代品,这里返回用户指定的默认值
return resource.finalTexture == myBack ? nullReturnValue : resource.finalTexture;
}
// 否则返回 null,主线程Runner正在处理它
return null;
} }
else
// 如果已经请求过,但还未处理完,则直接返回null,防止重复请求
if (_requestedSet.Contains(key))
{ {
Debug.LogWarning("Download failed for card: " + request.PicResource.code); return null;
LoadCardPicture(request.PicResource, request.FilePath); // Let it handle the missing file
} }
});
}
private static Thread main_thread; // 创建新请求
PictureResource newResource = new PictureResource(type, code, nullReturnValue);
// MODIFIED: In your original code, Program.Running was used. This is a more robust way. _requestStack.Push(newResource);
private static bool IsRunning = true; _requestedSet.Add(key);
// 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)
{
main_thread.Join(100);
} }
return null;
} }
static void thread_run()
public static float getK(long code, GameTextureType type)
{ {
while (IsRunning) UInt64 key = hashPic(code, type);
lock (_lock)
{ {
try PictureResource r;
{ if (_loadedCache.TryGetValue(key, out r))
Thread.Sleep(50);
int thu = 0;
while (waitLoadStack.Count > 0)
{
thu++;
if (thu == 10)
{
Thread.Sleep(50);
thu = 0;
}
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());
}
switch (pic.type)
{
case GameTextureType.card_feature:
{
ProcessingCardFeature(pic);
break;
}
case GameTextureType.card_picture:
{
ProcessingCardPicture(pic);
break;
}
case GameTextureType.card_verticle_drawing:
{
ProcessingVerticleDrawing(pic);
break;
}
}
}
}
}
catch (Exception e)
{ {
Debug.Log("error 1" + e.ToString()); return r.k;
} }
} }
return 1.0f;
} }
private static void ProcessingCardFeature(PictureResource pic) #endregion
#region --- 背景线程处理 (Background I/O Thread) ---
// 背景线程只做文件I/O,不做任何Unity API调用
static void IoThreadRun()
{ {
try while (_isRunning)
{ {
if (File.Exists("picture/closeup/" + pic.code.ToString() + ".png")) PictureResource resourceToProcess = null;
lock (_lock)
{ {
string path = "picture/closeup/" + pic.code.ToString() + ".png"; if (_requestStack.Count > 0)
if (Program.ANDROID_API_N)
{ {
BitmapHelper bitmap = new BitmapHelper(path); resourceToProcess = _requestStack.Pop();
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 }
if (resourceToProcess != null)
{
try
{ {
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); resourceToProcess.pCard = (card.Type & (int)CardType.Pendulum) > 0;
data = new byte[file.Length];
file.Read(data, 0, (int)file.Length);
} }
pic.data = data;
} }
catch (Exception e)
if (!loadedList.ContainsKey(hashPic(pic.code, pic.type)))
{ {
loadedList.Add(hashPic(pic.code, pic.type), pic); Debug.LogWarning("Get card type failed for code " + resourceToProcess.code + ": " + e.Message);
} }
ProcessResourceRequest(resourceToProcess);
} }
else else
{ {
string path = "picture/card/" + pic.code.ToString() + ".png"; // 没有任务时短暂休眠
if (!File.Exists(path)) Thread.Sleep(30);
{
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";
}
if (!File.Exists(path))
{
pic.hashed_data = new float[10, 10, 4];
for (int w = 0; w < 10; w++)
{
for (int h = 0; h < 10; h++)
{
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;
}
}
if (!loadedList.ContainsKey(hashPic(pic.code, pic.type)))
{
loadedList.Add(hashPic(pic.code, pic.type), pic);
}
}
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++)
{
for (int h = 0; h < height; h++)
{
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)))
{
loadedList.Add(hashPic(pic.code, pic.type), pic);
}
}
} }
} }
catch (Exception e)
{
Debug.Log("e 1" + e.ToString());
}
} }
private static void caculateK(PictureResource pic) private static void ProcessResourceRequest(PictureResource pic)
{ {
int width = pic.hashed_data.GetLength(0); string path = FindImagePath(pic);
int height = pic.hashed_data.GetLength(1);
int h = 0; if (!string.IsNullOrEmpty(path) && File.Exists(path))
for (h = height - 1; h > 0; h--)
{ {
int all = 0; try
for (int w = 0; w < width; w++)
{ {
if (pic.hashed_data[w, h, 3] > 0.05f) pic.rawData = File.ReadAllBytes(path);
{
all += 1;
}
} }
if (all * 5 > width) catch (Exception e)
{ {
break; Debug.LogError("Failed to read file: " + path + ". Error: " + e.Message);
pic.rawData = null; // 确保失败时rawData为null
} }
} }
pic.k = ((float)h) / ((float)height); else if (pic.type == GameTextureType.card_picture && pic.code != 0 && AutoPicDownload)
if (pic.k > 1)
{ {
pic.k = 1f; // 文件不存在,且是卡图,则尝试下载
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)
{
_downloadRequestQueue.Enqueue(new DownloadRequest
{
Url = url,
FilePath = finalPath,
PicResource = pic
});
}
// 下载任务已入队,直接返回,等待下载回调来继续处理
return;
} }
if (pic.k < 0)
// I/O操作完成,将资源推入主线程处理队列
lock (_lock)
{ {
pic.k = 0.1f; _mainThreadApplyQueue.Enqueue(pic);
}
}
private static string FindImagePath(PictureResource pic)
{
// ... (原代码中的路径查找逻辑,这里可以保持或优化)
// 为了清晰,我把它提取成了一个独立方法
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;
case GameTextureType.card_feature:
case GameTextureType.card_verticle_drawing:
path = "picture/closeup/" + pic.code + ".png";
if (!File.Exists(path))
{
// 回退到卡图路径
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;
} }
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); if (success)
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 (pCard) Debug.Log("Download successful, reading file: " + request.FilePath);
try
{ {
left = (int)(16f * ((float)right) / 177f); request.PicResource.rawData = File.ReadAllBytes(request.FilePath);
right = (int)(162f * ((float)right) / 177f);
top = (int)(50f * ((float)buttom) / 254f);
buttom = (int)(158f * ((float)buttom) / 254f);
} }
else catch (Exception ex)
{ {
left = (int)(26f * ((float)right) / 177f); Debug.LogError("Failed to read downloaded file: " + ex.Message);
right = (int)(152f * ((float)right) / 177f); request.PicResource.rawData = null;
top = (int)(55f * ((float)buttom) / 254f);
buttom = (int)(180f * ((float)buttom) / 254f);
} }
} }
else else
{ {
if (pCard) Debug.LogWarning("Download failed for card: " + request.PicResource.code);
{ request.PicResource.rawData = null;
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);
}
} }
float[,,] returnValue = new float[right - left, buttom - top, 4];
for (int w = 0; w < right - left; w++) // 将任务推入主线程处理队列
lock (_lock)
{ {
for (int h = 0; h < buttom - top; h++) _mainThreadApplyQueue.Enqueue(request.PicResource);
{
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;
}
} }
return returnValue;
} }
private static int CutButton(BitmapHelper bitmap, int right) #endregion
#region --- 主线程纹理处理 (Main Thread Texture Creation) ---
// 这个方法由 GameTextureManagerRunner 在主线程的 Update 中调用
public static void CreateTextureFromResource(PictureResource pic)
{ {
for (int w = bitmap.colors.GetLength(0) - 1; w >= 0; w--) Texture2D texture = null;
// 步骤1: 加载原始图像数据
if (pic.rawData != null && pic.rawData.Length > 0)
{ {
for (int h = 0; h < bitmap.colors.GetLength(1); h++) // 使用LoadImage将字节数组解码为纹理
// 它会自动处理PNG/JPG格式
// 创建一个临时的2x2纹理,LoadImage会自动调整其大小
Texture2D tempTexture = new Texture2D(2, 2);
if (tempTexture.LoadImage(pic.rawData))
{ {
System.Drawing.Color color = bitmap.GetPixel(w, h); // 步骤2: 根据类型进行图像处理
if (color.A > 10) switch (pic.type)
{ {
right = w; case GameTextureType.card_picture:
return right; // 卡图直接使用,无需额外处理
texture = tempTexture;
// 因为我们直接用了tempTexture,所以不要销毁它
break;
case GameTextureType.card_feature:
texture = ProcessFeatureTexture(pic, tempTexture);
Destroy(tempTexture); // 销毁临时纹理
break;
case GameTextureType.card_verticle_drawing:
texture = ProcessVerticleDrawingTexture(pic, tempTexture);
Destroy(tempTexture); // 销毁临时纹理
break;
} }
} }
} else
return right;
}
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++)
{ {
System.Drawing.Color color = bitmap.GetPixel(w, h); Debug.LogWarning("LoadImage failed for card code: " + pic.code);
if (color.A > 10) Destroy(tempTexture);
{
down = h;
return down;
}
} }
} }
return down;
}
private static int CutLeft(BitmapHelper bitmap, int up) // 步骤3: 处理加载失败或无数据的情况
{ if (texture == null)
for (int h = 0; h < bitmap.colors.GetLength(1); h++)
{ {
for (int w = 0; w < bitmap.colors.GetLength(0); w++) if (pic.type == GameTextureType.card_picture)
{ {
System.Drawing.Color color = bitmap.GetPixel(w, h); texture = (pic.code > 0) ? unknown : myBack;
if (color.A > 10)
{
up = h;
return up;
}
} }
} else
return up;
}
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); // 对于feature等,失败时返回一个完全透明的小纹理
if (color.A > 10) texture = N;
{
left = w;
return;
}
} }
} }
}
private static void ProcessingVerticleDrawing(PictureResource pic) // 步骤4: 完成并缓存
{ pic.finalTexture = texture;
try pic.ClearIntermediateData(); // 释放中间数据内存
UInt64 key = hashPic(pic.code, pic.type);
lock (_lock)
{ {
string path = "picture/closeup/" + pic.code.ToString() + ".png"; if (!_loadedCache.ContainsKey(key))
if (!File.Exists(path))
{ {
if (Program.ANDROID_API_N) _loadedCache.Add(key, pic);
{
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";
}
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);
}
pic.data = data;
if (!loadedList.ContainsKey(hashPic(pic.code, pic.type)))
{
loadedList.Add(hashPic(pic.code, pic.type), pic);
}
}
} }
else else
{ {
LoadCloseupPicture(pic, path); // 如果因为某些原因(例如,下载失败后重试)已经存在,则更新它
_loadedCache[key] = pic;
} }
} }
catch (Exception e)
{
Debug.Log("e 3" + e.ToString());
}
} }
private static void LoadCloseupFromCardPicture(PictureResource pic, string path, bool Iam8) // 从Unity的Texture2D中裁剪出需要的区域
private static RectInt GetCardArtRect(PictureResource pic, int texWidth, int texHeight)
{ {
try // 根据原代码的比例进行裁剪
// 注意:原代码的路径有好几种,对应不同尺寸的卡图,这里简化为一种常见比例
// 你可能需要根据实际卡图源的尺寸来微调这些比值
// 以YGOPRO标准卡图尺寸 177x254 为例
float ratioW = (float)texWidth / 177f;
float ratioH = (float)texHeight / 254f;
int x, y, width, height;
if (pic.pCard) // 灵摆
{ {
if (!File.Exists(path)) x = (int)(16f * ratioW);
{ width = (int)((162f - 16f) * ratioW);
path = "picture/null.png"; y = (int)((254f - 158f) * ratioH); // Y坐标在Unity中从下往上
} height = (int)((158f - 50f) * ratioH);
if (!File.Exists(path))
{
path = "textures/unknown.jpg"; //YGOMobile Paths
}
pic.hashed_data = getCuttedPic(path, pic.pCard, Iam8);
softVtype(pic, 0.5f);
pic.k = 1;
if (!loadedList.ContainsKey(hashPic(pic.code, pic.type)))
{
loadedList.Add(hashPic(pic.code, pic.type), pic);
}
} }
catch { } else // 普通
{
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);
} }
private static void LoadCloseupPicture(PictureResource pic, string path) private static Texture2D ProcessFeatureTexture(PictureResource pic, Texture2D source)
{ {
try 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++)
{ {
if (Program.ANDROID_API_N) for (int col = 0; col < newWidth; col++)
{
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);
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)
{
wholeUNalpha += ((float)Math.Abs(w - width / 2)) / ((float)(width / 2));
}
if (pic.hashed_data[w, height - 1, 3] > 0.1f)
{
wholeUNalpha += 1;
}
}
for (int h = 0; h < height; h++)
{
if (pic.hashed_data[0, h, 3] > 0.1f)
{
wholeUNalpha += 1;
}
if (pic.hashed_data[width - 1, h, 3] > 0.1f)
{
wholeUNalpha += 1;
}
}
if (wholeUNalpha >= ((width + height) * 0.5f * 0.12f))
{
softVtype(pic, 0.7f);
}
caculateK(pic);
}
else
{ {
byte[] data; int sourceIndex = (cropRect.y + row) * sourceWidth + (cropRect.x + col);
using (FileStream file = new FileStream(path, FileMode.Open, FileAccess.Read)) int destIndex = row * newWidth + col;
{ newPixels[destIndex] = pixels[sourceIndex];
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))) // 应用边缘淡出效果
float fadeMargin = 40f;
for (int y = 0; y < newHeight; y++)
{
for (int x = 0; x < newWidth; x++)
{ {
loadedList.Add(hashPic(pic.code, pic.type), pic); float alpha = 1.0f;
// 计算到四条边的距离
float distToLeft = x;
float distToRight = newWidth - 1 - x;
float distToTop = newHeight - 1 - y;
float distToBottom = y;
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); // 对于立绘,我们通常直接使用整个 PNG,但要应用软边
int height = pic.hashed_data.GetLength(1); int width = source.width;
int size = (int)(height * si); int height = source.height;
Color32[] pixels = source.GetPixels32();
// 应用软边效果 (类似原softVtype)
float softenSizeRatio = 0.7f;
int size = (int)(height * softenSizeRatio);
int empWidth = (width - size) / 2; int empWidth = (width - size) / 2;
int empHeight = (height - size) / 2; int empHeight = (height - size) / 2;
int right = width - empWidth;
int buttom = height - empHeight; for (int y = 0; y < height; y++)
float dui = (float)Math.Sqrt((width / 2) * (width / 2) + (height / 2) * (height / 2));
for (int w = 0; w < width; w++)
{ {
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) if (empWidth > 0)
{ {
float l = (float) if (distToLeft < empWidth) alpha = Mathf.Min(alpha, distToLeft / empWidth);
Math.Sqrt( if (distToRight < empWidth) alpha = Mathf.Min(alpha, distToRight / empWidth);
(width / 2 - w) * (width / 2 - w) + (height / 2 - h) * (height / 2 - h) }
); if (empHeight > 0)
l -= width * 0.3f; {
if (l < 0) if (distToTop < empHeight) alpha = Mathf.Min(alpha, distToTop / empHeight);
{ if (distToBottom < empHeight) alpha = Mathf.Min(alpha, distToBottom / empHeight);
l = 0;
}
float alpha = 1f - l / (0.6f * (dui - width * 0.3f));
if (alpha < 0)
{
alpha = 0;
}
if (a > alpha)
a = alpha;
} }
if (w < empWidth) int index = y * width + x;
if (a > ((float)w) / (float)empWidth) pixels[index].a = (byte)(pixels[index].a * alpha);
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;
} }
} }
Texture2D finalTexture = new Texture2D(width, height, TextureFormat.RGBA32, false);
finalTexture.SetPixels32(pixels);
finalTexture.Apply();
CalculateKValue(pic, pixels, width, height);
return finalTexture;
} }
private static void ProcessingCardPicture(PictureResource pic) private static void CalculateKValue(PictureResource pic, Color32[] pixels, int width, int height)
{ {
try int lastVisibleRow = 0;
for (int y = height - 1; y >= 0; y--)
{ {
string path = "picture/card/" + pic.code.ToString() + ".png"; int opaquePixelsInRow = 0;
if (!File.Exists(path)) for (int x = 0; x < width; x++)
{ {
path = "picture/card/" + pic.code.ToString() + ".jpg"; if (pixels[y * width + x].a > 12) // 0.05 * 255 ≈ 12
}
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";
// Enqueue the request. The runner on the main thread will pick it up.
lock (downloadRequestQueue)
{ {
downloadRequestQueue.Enqueue(new DownloadRequest opaquePixelsInRow++;
{
Url = url,
FilePath = finalPath,
PicResource = pic
});
} }
} }
else if (opaquePixelsInRow * 5 > width)
{ {
LoadCardPicture(pic, path); lastVisibleRow = y;
break;
} }
} }
catch (Exception e) pic.k = (float)(lastVisibleRow + 1) / (float)height;
{ pic.k = Mathf.Clamp(pic.k, 0.1f, 1.0f);
Debug.Log("e 2" + e.ToString());
}
} }
private static void LoadCardPicture(PictureResource pic, string path)
#endregion
#region --- 工具方法和初始化 ---
private static UInt64 hashPic(long code, GameTextureType type)
{ {
try return ((UInt64)type << 32) | (UInt64)(uint)code;
}
private static void Destroy(UnityEngine.Object obj)
{
if (Application.isPlaying)
{ {
if (!File.Exists(path)) UnityEngine.Object.Destroy(obj);
{
if (pic.code > 0)
{
pic.u_data = unknown;
}
else
{
pic.u_data = myBack;
}
if (!loadedList.ContainsKey(hashPic(pic.code, pic.type)))
{
loadedList.Add(hashPic(pic.code, pic.type), pic);
}
}
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)))
{
loadedList.Add(hashPic(pic.code, pic.type), pic);
}
}
} }
catch (Exception e) else
{ {
Debug.Log("e 2" + e.ToString()); // 在编辑器模式下且非播放状态时使用 DestroyImmediate
UnityEngine.Object.DestroyImmediate(obj);
} }
} }
private static UInt64 hashPic(long code, GameTextureType type) // 省略UI部分和静态资源定义,它们与原版类似,但get(string)方法已优化
{ public static Texture2D myBack, opBack, unknown, attack, negated, bar, exBar, lp, time, L, R, Chain, Mask, N;
return (((UInt64)type << 32) | ((UInt64)code)); 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(long code, GameTextureType type, Texture2D nullReturnValue = null) public static Texture2D get(string name)
{ {
try if (!uiLoaded)
{ {
PictureResource r; uiLoaded = true;
if (loadedList.TryGetValue(hashPic(code, type), out r)) string uiPath = "textures/ui";
if (Directory.Exists(uiPath))
{ {
Texture2D re = null; FileInfo[] fileInfos = (new DirectoryInfo(uiPath)).GetFiles("*.png");
if (r.u_data != null) foreach (var fileInfo in fileInfos)
{
if (r.u_data == myBack)
{
return nullReturnValue;
}
else
{
return r.u_data;
}
}
if (r.data != null)
{
re = new Texture2D(400, 600);
re.LoadImage(r.data);
r.u_data = re;
return re;
}
if (r.hashed_data != null)
{ {
int width = r.hashed_data.GetLength(0); string key = Path.GetFileNameWithoutExtension(fileInfo.Name);
int height = r.hashed_data.GetLength(1); if (!_uiCache.ContainsKey(key))
UnityEngine.Color[] cols = new UnityEngine.Color[width * height];
re = new Texture2D(width, height);
for (int h = 0; h < height; h++)
{ {
for (int w = 0; w < width; w++) // 这里假设UIHelper.getTexture2D是同步加载,如果也是异步需要相应修改
{ Texture2D tex = UIHelper.getTexture2D(fileInfo.FullName);
cols[h * width + w] = new UnityEngine.Color( _uiCache.Add(key, tex);
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
{
if (!addedMap.ContainsKey(hashPic(code, type)))
{
PictureResource a = new PictureResource(type, code, nullReturnValue);
bLock = true;
waitLoadStack.Push(a);
bLock = false;
addedMap.Add((UInt64)type << 32 | (UInt64)code, true);
}
}
}
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; lock (_lock)
PictureResource r;
if (loadedList.TryGetValue(hashPic(code, type), out r))
{ {
ret = r.k; _requestStack.Clear();
_requestedSet.Clear();
} }
return ret;
} }
public static bool uiLoaded = false; public static void clearAll()
public static Texture2D get(string name)
{ {
if (uiLoaded == false) lock (_lock)
{ {
uiLoaded = true; _requestStack.Clear();
FileInfo[] fileInfos = (new DirectoryInfo("textures/ui")).GetFiles(); //YGOMobile Paths _mainThreadApplyQueue.Clear();
for (int i = 0; i < fileInfos.Length; i++)
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(); Destroy(pair.Value.finalTexture);
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);
} }
} }
} }
_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; // 初始化方法
public static void InitializeAssets()
internal static void Initialize()
{ {
if (IsInitialized) return;
attack = UIHelper.getTexture2D("textures/attack.png"); //YGOMobile Paths attack = UIHelper.getTexture2D("textures/attack.png"); //YGOMobile Paths
myBack = UIHelper.getTexture2D("textures/cover.jpg"); //YGOMobile Paths myBack = UIHelper.getTexture2D("textures/cover.jpg"); //YGOMobile Paths
opBack = UIHelper.getTexture2D("textures/cover2.jpg"); //YGOMobile Paths opBack = UIHelper.getTexture2D("textures/cover2.jpg"); //YGOMobile Paths
...@@ -1122,27 +696,33 @@ public class GameTextureManager ...@@ -1122,27 +696,33 @@ public class GameTextureManager
rs = UIHelper.getTexture2D("textures/duel/phase/rs.png"); //YGOMobile Paths rs = UIHelper.getTexture2D("textures/duel/phase/rs.png"); //YGOMobile Paths
ts = UIHelper.getTexture2D("textures/duel/phase/ts.png"); //YGOMobile Paths ts = UIHelper.getTexture2D("textures/duel/phase/ts.png"); //YGOMobile Paths
N = new Texture2D(10, 10); // 创建一个1x1的透明纹理,用于失败时的占位符
for (int i = 0; i < 10; i++) N = new Texture2D(1, 1, TextureFormat.RGBA32, false);
{ N.SetPixel(0, 0, new Color(0, 0, 0, 0));
for (int a = 0; a < 10; a++)
{
N.SetPixel(i, a, new UnityEngine.Color(0, 0, 0, 0));
}
}
N.Apply(); N.Apply();
try try
{ {
ColorUtility.TryParseHtmlString( ColorUtility.TryParseHtmlString(File.ReadAllText("textures/duel/chainColor.txt"), out chainColor);
File.ReadAllText("textures/duel/chainColor.txt"),
out chainColor
); //YGOMobile Paths
} }
catch (Exception) { } 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); _isRunning = true;
main_thread.Start(); _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:
...@@ -16,10 +16,20 @@ public class UnityFileDownloader ...@@ -16,10 +16,20 @@ public class UnityFileDownloader
Action<float> onProgress = null Action<float> onProgress = null
) )
{ {
string directoryPath = Path.GetDirectoryName(filePath); // 确保目录存在
if (!Directory.Exists(directoryPath)) try
{ {
Directory.CreateDirectory(directoryPath); 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"; string tempFilePath = filePath + ".tmp";
...@@ -28,6 +38,8 @@ public class UnityFileDownloader ...@@ -28,6 +38,8 @@ public class UnityFileDownloader
File.Delete(tempFilePath); File.Delete(tempFilePath);
} }
Debug.Log(string.Format("Downloading: {0} -> {1}", url, filePath));
using (UnityWebRequest uwr = new UnityWebRequest(url, UnityWebRequest.kHttpVerbGET)) using (UnityWebRequest uwr = new UnityWebRequest(url, UnityWebRequest.kHttpVerbGET))
{ {
uwr.downloadHandler = new DownloadHandlerFile(tempFilePath); uwr.downloadHandler = new DownloadHandlerFile(tempFilePath);
...@@ -80,15 +92,15 @@ public class UnityFileDownloader ...@@ -80,15 +92,15 @@ public class UnityFileDownloader
switch (extension) switch (extension)
{ {
case ".png": case ".png":
return 20; return 40;
case ".jpg": case ".jpg":
return 20; return 40;
case ".cdb": case ".cdb":
return 50; return 80;
case ".conf": case ".conf":
return 20; return 40;
default: 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