stash work
This commit is contained in:
committed by
Michal Pikulski
parent
8d410b42d3
commit
5bab6d9596
104
Assets/Scripts/Utils/AddressablesUtility.cs
Normal file
104
Assets/Scripts/Utils/AddressablesUtility.cs
Normal file
@@ -0,0 +1,104 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Core;
|
||||
using UnityEngine.AddressableAssets;
|
||||
using UnityEngine.ResourceManagement.AsyncOperations;
|
||||
|
||||
namespace Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility class for common Addressables operations.
|
||||
/// Provides generic methods for loading assets by label and creating lookup dictionaries.
|
||||
/// </summary>
|
||||
public static class AddressablesUtility
|
||||
{
|
||||
/// <summary>
|
||||
/// Load all assets with a specific label and create a dictionary indexed by a key selector.
|
||||
/// </summary>
|
||||
/// <typeparam name="TAsset">Type of asset to load</typeparam>
|
||||
/// <typeparam name="TKey">Type of key for dictionary</typeparam>
|
||||
/// <param name="label">Addressables label to filter by</param>
|
||||
/// <param name="keySelector">Function to extract key from asset (e.g., asset => asset.Id)</param>
|
||||
/// <param name="onProgress">Optional callback for progress updates (0-1)</param>
|
||||
/// <returns>Dictionary of assets indexed by key, and operation handle for cleanup</returns>
|
||||
public static async Task<(Dictionary<TKey, TAsset> dictionary, AsyncOperationHandle<IList<TAsset>> handle)>
|
||||
LoadAssetsByLabelAsync<TAsset, TKey>(
|
||||
string label,
|
||||
Func<TAsset, TKey> keySelector,
|
||||
Action<float> onProgress = null)
|
||||
{
|
||||
Dictionary<TKey, TAsset> dictionary = new Dictionary<TKey, TAsset>();
|
||||
|
||||
// Load all assets with the specified label
|
||||
AsyncOperationHandle<IList<TAsset>> handle = Addressables.LoadAssetsAsync<TAsset>(
|
||||
label,
|
||||
asset =>
|
||||
{
|
||||
// This callback is invoked for each asset as it loads
|
||||
if (asset != null)
|
||||
{
|
||||
TKey key = keySelector(asset);
|
||||
if (key != null && !dictionary.ContainsKey(key))
|
||||
{
|
||||
dictionary[key] = asset;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Report progress if callback provided
|
||||
while (!handle.IsDone)
|
||||
{
|
||||
onProgress?.Invoke(handle.PercentComplete);
|
||||
await Task.Yield();
|
||||
}
|
||||
|
||||
// Final progress update
|
||||
onProgress?.Invoke(1f);
|
||||
|
||||
// Check if load was successful
|
||||
if (handle.Status != AsyncOperationStatus.Succeeded)
|
||||
{
|
||||
Logging.Error($"[AddressablesUtility] Failed to load assets with label '{label}': {handle.OperationException?.Message}");
|
||||
return (dictionary, handle);
|
||||
}
|
||||
|
||||
Logging.Debug($"[AddressablesUtility] Loaded {dictionary.Count} assets with label '{label}'");
|
||||
|
||||
return (dictionary, handle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load a single asset by address
|
||||
/// </summary>
|
||||
/// <typeparam name="TAsset">Type of asset to load</typeparam>
|
||||
/// <param name="address">Addressable address/key</param>
|
||||
/// <returns>Loaded asset and operation handle for cleanup</returns>
|
||||
public static async Task<(TAsset asset, AsyncOperationHandle<TAsset> handle)> LoadAssetAsync<TAsset>(string address)
|
||||
{
|
||||
AsyncOperationHandle<TAsset> handle = Addressables.LoadAssetAsync<TAsset>(address);
|
||||
|
||||
await handle.Task;
|
||||
|
||||
if (handle.Status != AsyncOperationStatus.Succeeded)
|
||||
{
|
||||
Logging.Warning($"[AddressablesUtility] Failed to load asset '{address}': {handle.OperationException?.Message}");
|
||||
return (default(TAsset), handle);
|
||||
}
|
||||
|
||||
return (handle.Result, handle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Safely release an Addressables handle
|
||||
/// </summary>
|
||||
public static void ReleaseHandle<T>(AsyncOperationHandle<T> handle)
|
||||
{
|
||||
if (handle.IsValid())
|
||||
{
|
||||
Addressables.Release(handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
3
Assets/Scripts/Utils/AddressablesUtility.cs.meta
Normal file
3
Assets/Scripts/Utils/AddressablesUtility.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 81470e29c2d54df3967e373b71d18a0d
|
||||
timeCreated: 1764164457
|
||||
71
Assets/Scripts/Utils/PhotoCaptureConfig.cs
Normal file
71
Assets/Scripts/Utils/PhotoCaptureConfig.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// Capture types for different photo contexts
|
||||
/// </summary>
|
||||
public enum CaptureType
|
||||
{
|
||||
StatueMinigame,
|
||||
DivingMinigame
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configuration for a specific capture type
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class CaptureConfig
|
||||
{
|
||||
public string subFolder;
|
||||
public string photoPrefix;
|
||||
public string metadataPrefix;
|
||||
public string indexKey;
|
||||
|
||||
public CaptureConfig(string subFolder, string photoPrefix, string metadataPrefix, string indexKey)
|
||||
{
|
||||
this.subFolder = subFolder;
|
||||
this.photoPrefix = photoPrefix;
|
||||
this.metadataPrefix = metadataPrefix;
|
||||
this.indexKey = indexKey;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Static configuration registry for all capture types
|
||||
/// </summary>
|
||||
public static class PhotoCaptureConfigs
|
||||
{
|
||||
private static readonly Dictionary<CaptureType, CaptureConfig> Configs = new Dictionary<CaptureType, CaptureConfig>
|
||||
{
|
||||
[CaptureType.StatueMinigame] = new CaptureConfig(
|
||||
subFolder: "StatueMinigame",
|
||||
photoPrefix: "Statue_",
|
||||
metadataPrefix: "StatuePhoto_Meta_",
|
||||
indexKey: "StatuePhoto_Index"
|
||||
),
|
||||
|
||||
[CaptureType.DivingMinigame] = new CaptureConfig(
|
||||
subFolder: "DivingMinigame",
|
||||
photoPrefix: "Diving_",
|
||||
metadataPrefix: "DivingPhoto_Meta_",
|
||||
indexKey: "DivingPhoto_Index"
|
||||
)
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Get configuration for a specific capture type
|
||||
/// </summary>
|
||||
public static CaptureConfig GetConfig(CaptureType type)
|
||||
{
|
||||
if (Configs.TryGetValue(type, out CaptureConfig config))
|
||||
{
|
||||
return config;
|
||||
}
|
||||
|
||||
throw new ArgumentException($"No configuration found for CaptureType: {type}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
3
Assets/Scripts/Utils/PhotoCaptureConfig.cs.meta
Normal file
3
Assets/Scripts/Utils/PhotoCaptureConfig.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 96dbe33216574c7f95d9a829f7768e69
|
||||
timeCreated: 1764159658
|
||||
635
Assets/Scripts/Utils/PhotoManager.cs
Normal file
635
Assets/Scripts/Utils/PhotoManager.cs
Normal file
@@ -0,0 +1,635 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Core;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// Generalized photo capture, storage, and retrieval manager.
|
||||
/// Supports multiple capture types (minigames, screenshots, etc.) with type-based configuration.
|
||||
/// </summary>
|
||||
public static class PhotoManager
|
||||
{
|
||||
private const string RootCapturesFolder = "Captures";
|
||||
|
||||
/// <summary>
|
||||
/// Photo metadata stored in PlayerPrefs
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class PhotoMetadata
|
||||
{
|
||||
public string photoId;
|
||||
public CaptureType captureType;
|
||||
public string timestamp;
|
||||
public int genericMetadata; // Can be decoration count, score, collectibles, etc.
|
||||
public long fileSizeBytes;
|
||||
}
|
||||
|
||||
#region Plug-and-Play Coroutine
|
||||
|
||||
/// <summary>
|
||||
/// Capture and save photo in one coroutine call. Handles UI hiding, capture, save, and restoration.
|
||||
/// </summary>
|
||||
/// <param name="captureType">Type of capture (determines folder/prefix)</param>
|
||||
/// <param name="captureArea">RectTransform defining the capture region</param>
|
||||
/// <param name="uiToHide">Optional UI elements to hide during capture</param>
|
||||
/// <param name="onSuccess">Callback with photoId on success</param>
|
||||
/// <param name="onFailure">Callback with error message on failure</param>
|
||||
/// <param name="metadata">Generic metadata (decoration count, score, etc.)</param>
|
||||
/// <param name="mainCamera">Camera for coordinate conversion (null = Camera.main)</param>
|
||||
/// <param name="clampToScreenBounds">If true, clamps capture area to visible screen</param>
|
||||
public static IEnumerator CaptureAndSaveCoroutine(
|
||||
CaptureType captureType,
|
||||
RectTransform captureArea,
|
||||
GameObject[] uiToHide = null,
|
||||
Action<string> onSuccess = null,
|
||||
Action<string> onFailure = null,
|
||||
int metadata = 0,
|
||||
Camera mainCamera = null,
|
||||
bool clampToScreenBounds = true)
|
||||
{
|
||||
if (captureArea == null)
|
||||
{
|
||||
string error = "[PhotoManager] CaptureArea RectTransform is null!";
|
||||
Logging.Error(error);
|
||||
onFailure?.Invoke(error);
|
||||
yield break;
|
||||
}
|
||||
|
||||
// Hide UI elements
|
||||
if (uiToHide != null)
|
||||
{
|
||||
foreach (var obj in uiToHide)
|
||||
{
|
||||
if (obj != null) obj.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for UI to hide
|
||||
yield return new WaitForEndOfFrame();
|
||||
|
||||
// Capture photo
|
||||
bool captureComplete = false;
|
||||
Texture2D capturedPhoto = null;
|
||||
|
||||
CaptureAreaPhoto(captureArea, (texture) =>
|
||||
{
|
||||
capturedPhoto = texture;
|
||||
captureComplete = true;
|
||||
}, mainCamera, clampToScreenBounds);
|
||||
|
||||
// Wait for capture to complete
|
||||
yield return new WaitUntil(() => captureComplete);
|
||||
|
||||
// Restore UI elements
|
||||
if (uiToHide != null)
|
||||
{
|
||||
foreach (var obj in uiToHide)
|
||||
{
|
||||
if (obj != null) obj.SetActive(true);
|
||||
}
|
||||
}
|
||||
|
||||
// Save photo
|
||||
if (capturedPhoto != null)
|
||||
{
|
||||
string photoId = SavePhoto(captureType, capturedPhoto, metadata);
|
||||
|
||||
if (!string.IsNullOrEmpty(photoId))
|
||||
{
|
||||
Logging.Debug($"[PhotoManager] Photo saved successfully: {photoId}");
|
||||
onSuccess?.Invoke(photoId);
|
||||
}
|
||||
else
|
||||
{
|
||||
string error = "[PhotoManager] Failed to save photo!";
|
||||
Logging.Error(error);
|
||||
onFailure?.Invoke(error);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string error = "[PhotoManager] Photo capture returned null!";
|
||||
Logging.Error(error);
|
||||
onFailure?.Invoke(error);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Capture
|
||||
|
||||
/// <summary>
|
||||
/// Capture a specific area of the screen using Screenshot Helper
|
||||
/// </summary>
|
||||
public static void CaptureAreaPhoto(
|
||||
RectTransform captureArea,
|
||||
Action<Texture2D> onComplete,
|
||||
Camera mainCamera = null,
|
||||
bool clampToScreenBounds = true)
|
||||
{
|
||||
if (captureArea == null)
|
||||
{
|
||||
Logging.Error("[PhotoManager] CaptureArea RectTransform is null!");
|
||||
onComplete?.Invoke(null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mainCamera == null) mainCamera = Camera.main;
|
||||
|
||||
// Use ScreenSpaceUtility to convert RectTransform to screen rect
|
||||
Rect screenRect = ScreenSpaceUtility.RectTransformToScreenRect(
|
||||
captureArea,
|
||||
mainCamera,
|
||||
clampToScreenBounds,
|
||||
returnCenterPosition: true
|
||||
);
|
||||
|
||||
Logging.Debug($"[PhotoManager] Capturing area: pos={screenRect.position}, size={screenRect.size}");
|
||||
|
||||
// Use Screenshot Helper's Capture method
|
||||
ScreenshotHelper.Instance.Capture(
|
||||
screenRect.position,
|
||||
screenRect.size,
|
||||
(texture) =>
|
||||
{
|
||||
if (texture != null)
|
||||
{
|
||||
Logging.Debug($"[PhotoManager] Photo captured: {texture.width}x{texture.height}");
|
||||
onComplete?.Invoke(texture);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logging.Error("[PhotoManager] Screenshot Helper returned null texture!");
|
||||
onComplete?.Invoke(null);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Save/Load
|
||||
|
||||
/// <summary>
|
||||
/// Save photo to persistent storage with metadata
|
||||
/// </summary>
|
||||
public static string SavePhoto(CaptureType captureType, Texture2D photo, int metadata = 0)
|
||||
{
|
||||
if (photo == null)
|
||||
{
|
||||
Logging.Error("[PhotoManager] Cannot save null photo");
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
CaptureConfig config = PhotoCaptureConfigs.GetConfig(captureType);
|
||||
|
||||
// Generate unique photo ID
|
||||
string photoId = $"{config.photoPrefix}{DateTime.Now.Ticks}";
|
||||
|
||||
// Get capture directory for this type
|
||||
string captureDirectory = GetCaptureDirectory(captureType);
|
||||
|
||||
// Ensure directory exists
|
||||
if (!Directory.Exists(captureDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(captureDirectory);
|
||||
}
|
||||
|
||||
// Save texture
|
||||
string filePath = Path.Combine(captureDirectory, $"{photoId}.png");
|
||||
byte[] pngData = photo.EncodeToPNG();
|
||||
File.WriteAllBytes(filePath, pngData);
|
||||
|
||||
// Calculate file size
|
||||
FileInfo fileInfo = new FileInfo(filePath);
|
||||
long fileSize = fileInfo.Exists ? fileInfo.Length : 0;
|
||||
|
||||
// Save metadata
|
||||
PhotoMetadata photoMetadata = new PhotoMetadata
|
||||
{
|
||||
photoId = photoId,
|
||||
captureType = captureType,
|
||||
timestamp = DateTime.Now.ToString("o"),
|
||||
genericMetadata = metadata,
|
||||
fileSizeBytes = fileSize
|
||||
};
|
||||
|
||||
SaveMetadata(captureType, photoMetadata);
|
||||
AddToPhotoIndex(captureType, photoId);
|
||||
|
||||
Logging.Debug($"[PhotoManager] Photo saved: {filePath} ({fileSize} bytes)");
|
||||
return photoId;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logging.Error($"[PhotoManager] Failed to save photo: {e.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load photo texture from storage
|
||||
/// </summary>
|
||||
public static Texture2D LoadPhoto(CaptureType captureType, string photoId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(photoId))
|
||||
{
|
||||
Logging.Warning("[PhotoManager] PhotoId is null or empty");
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
string filePath = GetPhotoFilePath(captureType, photoId);
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
Logging.Warning($"[PhotoManager] Photo not found: {filePath}");
|
||||
return null;
|
||||
}
|
||||
|
||||
byte[] fileData = File.ReadAllBytes(filePath);
|
||||
Texture2D texture = new Texture2D(2, 2, TextureFormat.RGBA32, false);
|
||||
|
||||
if (texture.LoadImage(fileData))
|
||||
{
|
||||
Logging.Debug($"[PhotoManager] Photo loaded: {photoId} ({texture.width}x{texture.height})");
|
||||
return texture;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logging.Error($"[PhotoManager] Failed to decode image: {photoId}");
|
||||
UnityEngine.Object.Destroy(texture);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logging.Error($"[PhotoManager] Failed to load photo {photoId}: {e.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load multiple photos (most recent first)
|
||||
/// </summary>
|
||||
public static List<Texture2D> LoadPhotos(CaptureType captureType, int count)
|
||||
{
|
||||
List<string> photoIds = GetPhotoIds(captureType, count);
|
||||
List<Texture2D> photos = new List<Texture2D>();
|
||||
|
||||
foreach (string photoId in photoIds)
|
||||
{
|
||||
Texture2D photo = LoadPhoto(captureType, photoId);
|
||||
if (photo != null)
|
||||
{
|
||||
photos.Add(photo);
|
||||
}
|
||||
}
|
||||
|
||||
return photos;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load all photos for a capture type
|
||||
/// </summary>
|
||||
public static List<Texture2D> LoadAllPhotos(CaptureType captureType)
|
||||
{
|
||||
return LoadPhotos(captureType, -1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete photo and its metadata
|
||||
/// </summary>
|
||||
public static bool DeletePhoto(CaptureType captureType, string photoId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(photoId)) return false;
|
||||
|
||||
try
|
||||
{
|
||||
string filePath = GetPhotoFilePath(captureType, photoId);
|
||||
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
File.Delete(filePath);
|
||||
}
|
||||
|
||||
DeleteMetadata(captureType, photoId);
|
||||
RemoveFromPhotoIndex(captureType, photoId);
|
||||
|
||||
Logging.Debug($"[PhotoManager] Photo deleted: {photoId}");
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logging.Error($"[PhotoManager] Failed to delete photo {photoId}: {e.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Retrieval & Queries
|
||||
|
||||
/// <summary>
|
||||
/// Get photo IDs for a capture type (most recent first)
|
||||
/// </summary>
|
||||
/// <param name="captureType">Type of capture</param>
|
||||
/// <param name="count">Number of IDs to return (-1 = all)</param>
|
||||
public static List<string> GetPhotoIds(CaptureType captureType, int count = -1)
|
||||
{
|
||||
List<string> allIds = GetAllPhotoIds(captureType);
|
||||
|
||||
if (count < 0 || count >= allIds.Count)
|
||||
{
|
||||
return allIds;
|
||||
}
|
||||
|
||||
return allIds.GetRange(0, count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all photo IDs sorted by timestamp (newest first)
|
||||
/// </summary>
|
||||
public static List<string> GetAllPhotoIds(CaptureType captureType)
|
||||
{
|
||||
CaptureConfig config = PhotoCaptureConfigs.GetConfig(captureType);
|
||||
string indexJson = PlayerPrefs.GetString(config.indexKey, "[]");
|
||||
List<string> photoIds = JsonUtility.FromJson<PhotoIdList>(WrapJsonArray(indexJson))?.ids ?? new List<string>();
|
||||
|
||||
// Sort by timestamp descending (newest first)
|
||||
photoIds.Sort((a, b) =>
|
||||
{
|
||||
PhotoMetadata metaA = LoadMetadata(captureType, a);
|
||||
PhotoMetadata metaB = LoadMetadata(captureType, b);
|
||||
|
||||
DateTime dateA = DateTime.Parse(metaA?.timestamp ?? DateTime.MinValue.ToString("o"));
|
||||
DateTime dateB = DateTime.Parse(metaB?.timestamp ?? DateTime.MinValue.ToString("o"));
|
||||
|
||||
return dateB.CompareTo(dateA);
|
||||
});
|
||||
|
||||
return photoIds;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get paginated photo IDs for optimized gallery loading
|
||||
/// </summary>
|
||||
public static List<string> GetPhotoIdsPage(CaptureType captureType, int page, int pageSize)
|
||||
{
|
||||
List<string> allIds = GetAllPhotoIds(captureType);
|
||||
int startIndex = page * pageSize;
|
||||
|
||||
if (startIndex >= allIds.Count) return new List<string>();
|
||||
|
||||
int count = Mathf.Min(pageSize, allIds.Count - startIndex);
|
||||
return allIds.GetRange(startIndex, count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get total number of saved photos
|
||||
/// </summary>
|
||||
public static int GetPhotoCount(CaptureType captureType)
|
||||
{
|
||||
return GetAllPhotoIds(captureType).Count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get latest photo ID (most recent)
|
||||
/// </summary>
|
||||
public static string GetLatestPhotoId(CaptureType captureType)
|
||||
{
|
||||
List<string> allIds = GetAllPhotoIds(captureType);
|
||||
return allIds.Count > 0 ? allIds[0] : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load photo metadata
|
||||
/// </summary>
|
||||
public static PhotoMetadata GetPhotoMetadata(CaptureType captureType, string photoId)
|
||||
{
|
||||
return LoadMetadata(captureType, photoId);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Utility Methods
|
||||
|
||||
/// <summary>
|
||||
/// Create thumbnail from full-size photo (for gallery preview)
|
||||
/// </summary>
|
||||
public static Texture2D CreateThumbnail(Texture2D fullSizePhoto, int maxSize = 256)
|
||||
{
|
||||
if (fullSizePhoto == null) return null;
|
||||
|
||||
int width = fullSizePhoto.width;
|
||||
int height = fullSizePhoto.height;
|
||||
|
||||
// Calculate thumbnail size maintaining aspect ratio
|
||||
float scale = Mathf.Min((float)maxSize / width, (float)maxSize / height);
|
||||
int thumbWidth = Mathf.RoundToInt(width * scale);
|
||||
int thumbHeight = Mathf.RoundToInt(height * scale);
|
||||
|
||||
// Create thumbnail using bilinear filtering
|
||||
RenderTexture rt = RenderTexture.GetTemporary(thumbWidth, thumbHeight, 0, RenderTextureFormat.ARGB32);
|
||||
RenderTexture.active = rt;
|
||||
|
||||
Graphics.Blit(fullSizePhoto, rt);
|
||||
|
||||
Texture2D thumbnail = new Texture2D(thumbWidth, thumbHeight, TextureFormat.RGB24, false);
|
||||
thumbnail.ReadPixels(new Rect(0, 0, thumbWidth, thumbHeight), 0, 0);
|
||||
thumbnail.Apply();
|
||||
|
||||
RenderTexture.active = null;
|
||||
RenderTexture.ReleaseTemporary(rt);
|
||||
|
||||
return thumbnail;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get capture directory for a specific type
|
||||
/// </summary>
|
||||
public static string GetCaptureDirectory(CaptureType captureType)
|
||||
{
|
||||
CaptureConfig config = PhotoCaptureConfigs.GetConfig(captureType);
|
||||
return Path.Combine(Application.persistentDataPath, RootCapturesFolder, config.subFolder);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Decoration Metadata
|
||||
|
||||
/// <summary>
|
||||
/// Save decoration metadata for a photo
|
||||
/// Saves both with photo ID and as "latest" for easy reference
|
||||
/// </summary>
|
||||
public static void SaveDecorationMetadata<T>(CaptureType captureType, string photoId, T metadata) where T : class
|
||||
{
|
||||
try
|
||||
{
|
||||
string directory = GetCaptureDirectory(captureType);
|
||||
|
||||
// Ensure directory exists
|
||||
if (!Directory.Exists(directory))
|
||||
{
|
||||
Directory.CreateDirectory(directory);
|
||||
}
|
||||
|
||||
string json = JsonUtility.ToJson(metadata, true);
|
||||
|
||||
// Save with photo ID (for loading specific photo's decorations)
|
||||
string specificPath = Path.Combine(directory, $"{photoId}_decorations.json");
|
||||
File.WriteAllText(specificPath, json);
|
||||
|
||||
// Also save as "latest" for easy reference
|
||||
CaptureConfig config = PhotoCaptureConfigs.GetConfig(captureType);
|
||||
string latestPath = Path.Combine(directory, $"{config.subFolder}_latest.json");
|
||||
File.WriteAllText(latestPath, json);
|
||||
|
||||
Logging.Debug($"[PhotoManager] Decoration metadata saved: {specificPath}");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logging.Error($"[PhotoManager] Failed to save decoration metadata: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load decoration metadata for a specific photo
|
||||
/// </summary>
|
||||
public static T LoadDecorationMetadata<T>(CaptureType captureType, string photoId) where T : class
|
||||
{
|
||||
try
|
||||
{
|
||||
string directory = GetCaptureDirectory(captureType);
|
||||
string filePath = Path.Combine(directory, $"{photoId}_decorations.json");
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
Logging.Warning($"[PhotoManager] Decoration metadata not found: {filePath}");
|
||||
return null;
|
||||
}
|
||||
|
||||
string json = File.ReadAllText(filePath);
|
||||
T metadata = JsonUtility.FromJson<T>(json);
|
||||
|
||||
Logging.Debug($"[PhotoManager] Decoration metadata loaded: {filePath}");
|
||||
return metadata;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logging.Error($"[PhotoManager] Failed to load decoration metadata: {e.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load the latest decoration metadata (most recent capture)
|
||||
/// </summary>
|
||||
public static T LoadLatestDecorationMetadata<T>(CaptureType captureType) where T : class
|
||||
{
|
||||
try
|
||||
{
|
||||
CaptureConfig config = PhotoCaptureConfigs.GetConfig(captureType);
|
||||
string directory = GetCaptureDirectory(captureType);
|
||||
string filePath = Path.Combine(directory, $"{config.subFolder}_latest.json");
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
Logging.Warning($"[PhotoManager] Latest decoration metadata not found: {filePath}");
|
||||
return null;
|
||||
}
|
||||
|
||||
string json = File.ReadAllText(filePath);
|
||||
T metadata = JsonUtility.FromJson<T>(json);
|
||||
|
||||
Logging.Debug($"[PhotoManager] Latest decoration metadata loaded: {filePath}");
|
||||
return metadata;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logging.Error($"[PhotoManager] Failed to load latest decoration metadata: {e.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Internal Helpers
|
||||
|
||||
private static string GetPhotoFilePath(CaptureType captureType, string photoId)
|
||||
{
|
||||
return Path.Combine(GetCaptureDirectory(captureType), $"{photoId}.png");
|
||||
}
|
||||
|
||||
private static void SaveMetadata(CaptureType captureType, PhotoMetadata metadata)
|
||||
{
|
||||
CaptureConfig config = PhotoCaptureConfigs.GetConfig(captureType);
|
||||
string json = JsonUtility.ToJson(metadata);
|
||||
PlayerPrefs.SetString(config.metadataPrefix + metadata.photoId, json);
|
||||
PlayerPrefs.Save();
|
||||
}
|
||||
|
||||
private static PhotoMetadata LoadMetadata(CaptureType captureType, string photoId)
|
||||
{
|
||||
CaptureConfig config = PhotoCaptureConfigs.GetConfig(captureType);
|
||||
string json = PlayerPrefs.GetString(config.metadataPrefix + photoId, null);
|
||||
return string.IsNullOrEmpty(json) ? null : JsonUtility.FromJson<PhotoMetadata>(json);
|
||||
}
|
||||
|
||||
private static void DeleteMetadata(CaptureType captureType, string photoId)
|
||||
{
|
||||
CaptureConfig config = PhotoCaptureConfigs.GetConfig(captureType);
|
||||
PlayerPrefs.DeleteKey(config.metadataPrefix + photoId);
|
||||
PlayerPrefs.Save();
|
||||
}
|
||||
|
||||
private static void AddToPhotoIndex(CaptureType captureType, string photoId)
|
||||
{
|
||||
List<string> photoIds = GetAllPhotoIds(captureType);
|
||||
if (!photoIds.Contains(photoId))
|
||||
{
|
||||
photoIds.Add(photoId);
|
||||
SavePhotoIndex(captureType, photoIds);
|
||||
}
|
||||
}
|
||||
|
||||
private static void RemoveFromPhotoIndex(CaptureType captureType, string photoId)
|
||||
{
|
||||
List<string> photoIds = GetAllPhotoIds(captureType);
|
||||
if (photoIds.Remove(photoId))
|
||||
{
|
||||
SavePhotoIndex(captureType, photoIds);
|
||||
}
|
||||
}
|
||||
|
||||
private static void SavePhotoIndex(CaptureType captureType, List<string> photoIds)
|
||||
{
|
||||
CaptureConfig config = PhotoCaptureConfigs.GetConfig(captureType);
|
||||
string json = JsonUtility.ToJson(new PhotoIdList { ids = photoIds });
|
||||
PlayerPrefs.SetString(config.indexKey, json);
|
||||
PlayerPrefs.Save();
|
||||
}
|
||||
|
||||
private static string WrapJsonArray(string json)
|
||||
{
|
||||
if (json.StartsWith("[")) return "{\"ids\":" + json + "}";
|
||||
return json;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
private class PhotoIdList
|
||||
{
|
||||
public List<string> ids = new List<string>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
3
Assets/Scripts/Utils/PhotoManager.cs.meta
Normal file
3
Assets/Scripts/Utils/PhotoManager.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c558069d1a8e46febc1c3716e9b76490
|
||||
timeCreated: 1764159714
|
||||
Reference in New Issue
Block a user