stash work

This commit is contained in:
Michal Pikulski
2025-11-26 17:11:02 +01:00
committed by Michal Pikulski
parent 8d410b42d3
commit 5bab6d9596
38 changed files with 3634 additions and 308 deletions

View File

@@ -0,0 +1,305 @@
using System.Collections.Generic;
using Core;
using Core.Lifecycle;
using Minigames.StatueDressup.Data;
using UnityEngine;
using UnityEngine.ResourceManagement.AsyncOperations;
using Utils;
namespace Minigames.StatueDressup.Display
{
/// <summary>
/// Loads decoration metadata and reconstructs decorations on a statue sprite.
/// Place this component on a GameObject with a SpriteRenderer showing the statue.
/// On Start, loads all DecorationData via Addressables label, then spawns decorations from metadata.
/// </summary>
[RequireComponent(typeof(SpriteRenderer))]
public class StatueDecorationLoader : ManagedBehaviour
{
[Header("Settings")]
[Tooltip("Root GameObject for spawning decorations (clears only this, not statue children)")]
[SerializeField] private Transform decorationRoot;
[Tooltip("Load specific photo ID, or leave empty to load latest")]
[SerializeField] private string specificPhotoId = "";
[Header("Debug")]
[SerializeField] private bool showDebugInfo = true;
private SpriteRenderer _statueSpriteRenderer;
private Dictionary<string, DecorationData> _decorationDataDict;
private AsyncOperationHandle<IList<DecorationData>> _decorationDataHandle;
private AppleHills.Core.Settings.IStatueDressupSettings _settings;
internal override void OnManagedStart()
{
base.OnManagedStart();
_statueSpriteRenderer = GetComponent<SpriteRenderer>();
// Get settings
_settings = GameManager.GetSettingsObject<AppleHills.Core.Settings.IStatueDressupSettings>();
// Ensure decoration root exists
if (decorationRoot == null)
{
GameObject rootObj = new GameObject("DecorationRoot");
rootObj.transform.SetParent(transform, false);
decorationRoot = rootObj.transform;
if (showDebugInfo)
{
Logging.Debug("[StatueDecorationLoader] Created decoration root automatically");
}
}
// Start async loading via coroutine wrapper
StartCoroutine(LoadAndDisplayDecorationsCoroutine());
}
/// <summary>
/// Coroutine wrapper for async loading and display
/// </summary>
private System.Collections.IEnumerator LoadAndDisplayDecorationsCoroutine()
{
// Convert async Task to coroutine-compatible operation
var loadTask = LoadDecorationDataAsync();
// Wait for async operation to complete
while (!loadTask.IsCompleted)
{
yield return null;
}
// Check for exceptions
if (loadTask.IsFaulted)
{
Logging.Error($"[StatueDecorationLoader] Failed to load decoration data: {loadTask.Exception?.GetBaseException().Message}");
yield break;
}
// Load and display decorations
LoadAndDisplayDecorations();
}
/// <summary>
/// Load all DecorationData assets via Addressables and build lookup dictionary
/// </summary>
private async System.Threading.Tasks.Task LoadDecorationDataAsync()
{
string label = _settings?.DecorationDataLabel;
if (string.IsNullOrEmpty(label))
{
Logging.Error("[StatueDecorationLoader] Decoration data label not set in settings!");
return;
}
if (showDebugInfo)
{
Logging.Debug($"[StatueDecorationLoader] Loading DecorationData with label '{label}'...");
}
// Use utility to load all DecorationData and create dictionary by ID
var result = await AddressablesUtility.LoadAssetsByLabelAsync<DecorationData, string>(
label,
data => data.DecorationId, // Key selector: use DecorationId as key
progress => { /* Optional: could show loading bar */ }
);
_decorationDataDict = result.dictionary;
_decorationDataHandle = result.handle;
if (showDebugInfo)
{
Logging.Debug($"[StatueDecorationLoader] Loaded {_decorationDataDict.Count} DecorationData assets");
}
}
/// <summary>
/// Load decoration metadata and spawn decorations
/// </summary>
public void LoadAndDisplayDecorations()
{
// Check if DecorationData is loaded
if (_decorationDataDict == null || _decorationDataDict.Count == 0)
{
Logging.Warning("[StatueDecorationLoader] DecorationData not loaded yet. Cannot display decorations.");
return;
}
// Load metadata
StatueDecorationData data = string.IsNullOrEmpty(specificPhotoId)
? PhotoManager.LoadLatestDecorationMetadata<StatueDecorationData>(CaptureType.StatueMinigame)
: PhotoManager.LoadDecorationMetadata<StatueDecorationData>(CaptureType.StatueMinigame, specificPhotoId);
if (data == null)
{
Logging.Warning("[StatueDecorationLoader] No decoration metadata found");
return;
}
if (showDebugInfo)
{
Logging.Debug($"[StatueDecorationLoader] Loading {data.placements.Count} decorations from {data.photoId}");
Logging.Debug($"[StatueDecorationLoader] Source coordinate system: {data.coordinateSystem}, statue size: {data.sourceStatueSize}");
}
// Clear existing decorations (in case reloading)
ClearDecorations();
// Calculate coordinate conversion factor if needed
float conversionFactor = CalculateCoordinateConversion(data);
// Spawn each decoration synchronously (data already loaded)
int successCount = 0;
foreach (var placement in data.placements)
{
if (SpawnDecoration(placement, conversionFactor))
{
successCount++;
}
}
if (showDebugInfo)
{
Logging.Debug($"[StatueDecorationLoader] Successfully loaded {successCount}/{data.placements.Count} decorations");
}
}
/// <summary>
/// Calculate coordinate conversion factor between source and target coordinate systems
/// </summary>
private float CalculateCoordinateConversion(StatueDecorationData data)
{
// If source was world space and we're also world space, no conversion needed
if (data.coordinateSystem == CoordinateSystemType.WorldSpace)
{
if (showDebugInfo)
{
Logging.Debug("[StatueDecorationLoader] No coordinate conversion needed (WorldSpace → WorldSpace)");
}
return 1f;
}
// Source was UI RectTransform (pixels), target is WorldSpace (units)
// Need to convert from source statue pixel size to target statue world size
// Get target statue size (world units)
Vector2 targetStatueSize = Vector2.one;
if (_statueSpriteRenderer != null && _statueSpriteRenderer.sprite != null)
{
targetStatueSize = _statueSpriteRenderer.sprite.bounds.size;
}
// Calculate conversion factor (target size / source size)
float conversionX = targetStatueSize.x / data.sourceStatueSize.x;
float conversionY = targetStatueSize.y / data.sourceStatueSize.y;
// Use average of X and Y for uniform scaling (or could use separate X/Y)
float conversionFactor = (conversionX + conversionY) / 2f;
if (showDebugInfo)
{
Logging.Debug($"[StatueDecorationLoader] Coordinate conversion: UI({data.sourceStatueSize}) → World({targetStatueSize}) = factor {conversionFactor:F3}");
}
return conversionFactor;
}
/// <summary>
/// Spawn a single decoration from placement data
/// Looks up DecorationData from pre-loaded dictionary and applies coordinate conversion
/// </summary>
private bool SpawnDecoration(DecorationPlacement placement, float conversionFactor)
{
// Look up DecorationData from dictionary
if (!_decorationDataDict.TryGetValue(placement.decorationId, out DecorationData decorationData))
{
Logging.Warning($"[StatueDecorationLoader] DecorationData not found for ID: {placement.decorationId}");
return false;
}
// Get sprite from DecorationData
Sprite decorationSprite = decorationData.DecorationSprite;
if (decorationSprite == null)
{
Logging.Warning($"[StatueDecorationLoader] DecorationData has null sprite: {placement.decorationId}");
return false;
}
// Create GameObject for decoration
GameObject decorationObj = new GameObject($"Decoration_{placement.decorationId}");
decorationObj.transform.SetParent(decorationRoot, false); // false = keep local position
// Add SpriteRenderer
SpriteRenderer spriteRenderer = decorationObj.AddComponent<SpriteRenderer>();
spriteRenderer.sprite = decorationSprite;
spriteRenderer.sortingLayerName = "Foreground";
spriteRenderer.sortingOrder = _statueSpriteRenderer.sortingOrder + placement.sortingOrder;
// Apply transform with coordinate conversion
Vector3 convertedPosition = placement.localPosition * conversionFactor;
decorationObj.transform.localPosition = convertedPosition;
decorationObj.transform.localScale = placement.localScale;
decorationObj.transform.localEulerAngles = new Vector3(0, 0, placement.rotation);
if (showDebugInfo)
{
Logging.Debug($"[StatueDecorationLoader] Spawned: {placement.decorationId} at {convertedPosition} (original: {placement.localPosition}, factor: {conversionFactor:F3})");
}
return true;
}
/// <summary>
/// Clear all existing decorations from decorationRoot
/// </summary>
public void ClearDecorations()
{
if (decorationRoot == null) return;
// Remove all children from decoration root only
for (int i = decorationRoot.childCount - 1; i >= 0; i--)
{
if (Application.isPlaying)
{
Destroy(decorationRoot.GetChild(i).gameObject);
}
else
{
DestroyImmediate(decorationRoot.GetChild(i).gameObject);
}
}
}
/// <summary>
/// Cleanup - release Addressables handle
/// </summary>
private void OnDestroy()
{
// Release DecorationData handle
AddressablesUtility.ReleaseHandle(_decorationDataHandle);
}
/// <summary>
/// Reload decorations (useful for testing)
/// </summary>
[ContextMenu("Reload Decorations")]
public void ReloadDecorations()
{
LoadAndDisplayDecorations();
}
/// <summary>
/// Load specific photo's decorations
/// </summary>
public void LoadSpecificPhoto(string photoId)
{
specificPhotoId = photoId;
LoadAndDisplayDecorations();
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 50d0f4591bbd40fc81dc615fa465e0c5
timeCreated: 1764163758