Finalize capture taking

This commit is contained in:
Michal Pikulski
2025-11-26 10:58:34 +01:00
committed by Michal Pikulski
parent 5ad84ca3e8
commit 8d410b42d3
8 changed files with 411 additions and 42 deletions

View File

@@ -57,7 +57,8 @@ namespace Minigames.StatueDressup.Controllers
// Setup photo button
if (takePhotoButton != null)
{
takePhotoButton.onClick.AddListener(OnTakePhoto);
// TODO: Remove comment when ready
// takePhotoButton.onClick.AddListener(OnTakePhoto);
}
// Subscribe to menu controller for tracking placed items

View File

@@ -5,6 +5,7 @@ using Minigames.StatueDressup.Utils;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
using Utils;
namespace Minigames.StatueDressup.DragDrop
{

View File

@@ -1,10 +1,10 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Core;
using UnityEngine;
using SDev;
using ScreenUtils = Utils.ScreenSpaceUtility;
namespace Minigames.StatueDressup.Utils
{
@@ -15,15 +15,15 @@ namespace Minigames.StatueDressup.Utils
/// </summary>
public static class StatuePhotoManager
{
private const string PHOTO_FOLDER = "StatuePhotos";
private const string PHOTO_PREFIX = "MrCementStatue_";
private const string METADATA_KEY_PREFIX = "StatuePhoto_Meta_";
private const string PHOTO_INDEX_KEY = "StatuePhoto_Index";
private const string PhotoFolder = "StatuePhotos";
private const string PhotoPrefix = "MrCementStatue_";
private const string MetadataKeyPrefix = "StatuePhoto_Meta_";
private const string PhotoIndexKey = "StatuePhoto_Index";
/// <summary>
/// Photo metadata stored in PlayerPrefs
/// </summary>
[System.Serializable]
[Serializable]
public class PhotoMetadata
{
public string photoId;
@@ -40,7 +40,12 @@ namespace Minigames.StatueDressup.Utils
/// <param name="captureArea">RectTransform defining the capture region</param>
/// <param name="onComplete">Callback with captured Texture2D</param>
/// <param name="mainCamera">Camera used for coordinate conversion (null = Camera.main)</param>
public static void CaptureAreaPhoto(RectTransform captureArea, Action<Texture2D> onComplete, Camera mainCamera = null)
/// <param name="clampToScreenBounds">If true, clamps capture area to visible screen bounds</param>
public static void CaptureAreaPhoto(
RectTransform captureArea,
Action<Texture2D> onComplete,
Camera mainCamera = null,
bool clampToScreenBounds = true)
{
if (captureArea == null)
{
@@ -51,8 +56,14 @@ namespace Minigames.StatueDressup.Utils
if (mainCamera == null) mainCamera = Camera.main;
// Get screen rect from RectTransform
Rect screenRect = GetScreenRectFromRectTransform(captureArea, mainCamera);
// Use ScreenSpaceUtility to convert RectTransform to screen rect
// returnCenterPosition = true because ScreenshotHelper expects center position
Rect screenRect = ScreenUtils.RectTransformToScreenRect(
captureArea,
mainCamera,
clampToScreenBounds,
returnCenterPosition: true
);
Logging.Debug($"[StatuePhotoManager] Capturing area: pos={screenRect.position}, size={screenRect.size}");
@@ -60,7 +71,7 @@ namespace Minigames.StatueDressup.Utils
ScreenshotHelper.Instance.Capture(
screenRect.position,
screenRect.size,
(Texture2D texture) =>
(texture) =>
{
if (texture != null)
{
@@ -75,24 +86,7 @@ namespace Minigames.StatueDressup.Utils
}
);
}
/// <summary>
/// Convert RectTransform world corners to screen space rect
/// </summary>
private static Rect GetScreenRectFromRectTransform(RectTransform rectTransform, Camera camera)
{
Vector3[] corners = new Vector3[4];
rectTransform.GetWorldCorners(corners);
Vector2 min = RectTransformUtility.WorldToScreenPoint(camera, corners[0]);
Vector2 max = RectTransformUtility.WorldToScreenPoint(camera, corners[2]);
// Ensure positive dimensions
float width = Mathf.Abs(max.x - min.x);
float height = Mathf.Abs(max.y - min.y);
return new Rect(min.x, min.y, width, height);
}
#endregion
@@ -115,13 +109,13 @@ namespace Minigames.StatueDressup.Utils
try
{
// Generate unique photo ID
string photoId = $"{PHOTO_PREFIX}{DateTime.Now.Ticks}";
string photoId = $"{PhotoPrefix}{DateTime.Now.Ticks}";
// Save texture using FileSaveUtil
string savedPath = FileSaveUtil.Instance.SaveTextureAsPNG(
photo,
FileSaveUtil.AppPath.PersistentDataPath,
PHOTO_FOLDER,
PhotoFolder,
photoId
);
@@ -232,7 +226,7 @@ namespace Minigames.StatueDressup.Utils
/// </summary>
public static List<string> GetAllPhotoIds()
{
string indexJson = PlayerPrefs.GetString(PHOTO_INDEX_KEY, "[]");
string indexJson = PlayerPrefs.GetString(PhotoIndexKey, "[]");
List<string> photoIds = JsonUtility.FromJson<PhotoIdList>(WrapJsonArray(indexJson))?.ids ?? new List<string>();
// Sort by timestamp descending (newest first)
@@ -328,7 +322,7 @@ namespace Minigames.StatueDressup.Utils
public static string GetPhotoDirectory()
{
return Path.Combine(Application.persistentDataPath, PHOTO_FOLDER);
return Path.Combine(Application.persistentDataPath, PhotoFolder);
}
private static string GetPhotoFilePath(string photoId)
@@ -339,19 +333,19 @@ namespace Minigames.StatueDressup.Utils
private static void SaveMetadata(PhotoMetadata metadata)
{
string json = JsonUtility.ToJson(metadata);
PlayerPrefs.SetString(METADATA_KEY_PREFIX + metadata.photoId, json);
PlayerPrefs.SetString(MetadataKeyPrefix + metadata.photoId, json);
PlayerPrefs.Save();
}
private static PhotoMetadata LoadMetadata(string photoId)
{
string json = PlayerPrefs.GetString(METADATA_KEY_PREFIX + photoId, null);
string json = PlayerPrefs.GetString(MetadataKeyPrefix + photoId, null);
return string.IsNullOrEmpty(json) ? null : JsonUtility.FromJson<PhotoMetadata>(json);
}
private static void DeleteMetadata(string photoId)
{
PlayerPrefs.DeleteKey(METADATA_KEY_PREFIX + photoId);
PlayerPrefs.DeleteKey(MetadataKeyPrefix + photoId);
PlayerPrefs.Save();
}
@@ -377,7 +371,7 @@ namespace Minigames.StatueDressup.Utils
private static void SavePhotoIndex(List<string> photoIds)
{
string json = JsonUtility.ToJson(new PhotoIdList { ids = photoIds });
PlayerPrefs.SetString(PHOTO_INDEX_KEY, json);
PlayerPrefs.SetString(PhotoIndexKey, json);
PlayerPrefs.Save();
}
@@ -387,7 +381,7 @@ namespace Minigames.StatueDressup.Utils
return json;
}
[System.Serializable]
[Serializable]
private class PhotoIdList
{
public List<string> ids = new List<string>();

View File

@@ -0,0 +1,198 @@
using Core;
using UnityEngine;
namespace Utils
{
/// <summary>
/// Utility methods for screen space and UI coordinate conversions
/// </summary>
public static class ScreenSpaceUtility
{
/// <summary>
/// Convert RectTransform to screen space rect with center position.
/// Useful for screenshot capture or screen-based calculations.
/// </summary>
/// <param name="rectTransform">RectTransform to convert</param>
/// <param name="camera">Camera used for coordinate conversion (null = Camera.main)</param>
/// <param name="clampToScreenBounds">If true, clamps the rect to visible screen area</param>
/// <param name="returnCenterPosition">If true, returns center position; if false, returns bottom-left position</param>
/// <returns>Screen space rect (position is center or bottom-left based on returnCenterPosition)</returns>
public static Rect RectTransformToScreenRect(
RectTransform rectTransform,
Camera camera = null,
bool clampToScreenBounds = true,
bool returnCenterPosition = true)
{
if (rectTransform == null)
{
Logging.Error("[ScreenSpaceUtility] RectTransform is null!");
return new Rect(Screen.width / 2f, Screen.height / 2f, 0, 0);
}
if (camera == null) camera = Camera.main;
// Get world corners (0=bottom-left, 1=top-left, 2=top-right, 3=bottom-right)
Vector3[] corners = new Vector3[4];
rectTransform.GetWorldCorners(corners);
// Determine correct camera based on Canvas render mode
Camera canvasCamera = GetCanvasCamera(rectTransform, camera);
// Convert corners to screen space
Vector2 min = RectTransformUtility.WorldToScreenPoint(canvasCamera, corners[0]);
Vector2 max = RectTransformUtility.WorldToScreenPoint(canvasCamera, corners[2]);
Logging.Debug($"[ScreenSpaceUtility] Canvas mode: {rectTransform.GetComponentInParent<Canvas>()?.renderMode}, Camera: {canvasCamera?.name ?? "null"}");
Logging.Debug($"[ScreenSpaceUtility] Raw screen coords - min: {min}, max: {max}");
// Apply screen bounds clamping if requested
if (clampToScreenBounds)
{
return ClampRectToScreenBounds(min, max, returnCenterPosition);
}
else
{
// No clamping - use raw values
float width = Mathf.Abs(max.x - min.x);
float height = Mathf.Abs(max.y - min.y);
if (returnCenterPosition)
{
float centerX = min.x + width / 2f;
float centerY = min.y + height / 2f;
return new Rect(centerX, centerY, width, height);
}
else
{
return new Rect(min.x, min.y, width, height);
}
}
}
/// <summary>
/// Get the appropriate camera for UI coordinate conversion based on Canvas render mode
/// </summary>
/// <param name="rectTransform">RectTransform to find canvas for</param>
/// <param name="fallbackCamera">Fallback camera if no canvas found</param>
/// <returns>Camera to use for coordinate conversion (null for Overlay mode)</returns>
public static Camera GetCanvasCamera(RectTransform rectTransform, Camera fallbackCamera = null)
{
Canvas canvas = rectTransform.GetComponentInParent<Canvas>();
if (canvas == null)
{
return fallbackCamera;
}
switch (canvas.renderMode)
{
case RenderMode.ScreenSpaceOverlay:
// For Screen Space - Overlay, use null (direct pixel coords)
return null;
case RenderMode.ScreenSpaceCamera:
// For Screen Space - Camera, use the canvas's worldCamera
return canvas.worldCamera;
case RenderMode.WorldSpace:
// For World Space, use canvas camera or fallback
return canvas.worldCamera ?? fallbackCamera;
default:
return fallbackCamera;
}
}
/// <summary>
/// Clamp rect coordinates to screen bounds and calculate final rect
/// </summary>
/// <param name="min">Bottom-left corner in screen space</param>
/// <param name="max">Top-right corner in screen space</param>
/// <param name="returnCenterPosition">If true, returns center position; if false, returns bottom-left</param>
/// <returns>Clamped screen rect</returns>
private static Rect ClampRectToScreenBounds(Vector2 min, Vector2 max, bool returnCenterPosition)
{
// Clamp to screen bounds
float minX = Mathf.Max(0, min.x);
float minY = Mathf.Max(0, min.y);
float maxX = Mathf.Min(Screen.width, max.x);
float maxY = Mathf.Min(Screen.height, max.y);
// Check if rect is completely outside screen bounds
if (minX >= Screen.width || minY >= Screen.height || maxX <= 0 || maxY <= 0)
{
Logging.Warning("[ScreenSpaceUtility] RectTransform is completely outside screen bounds!");
return new Rect(Screen.width / 2f, Screen.height / 2f, 0, 0);
}
// Calculate dimensions from clamped bounds
float width = maxX - minX;
float height = maxY - minY;
// Validate dimensions
if (width <= 0 || height <= 0)
{
Logging.Warning($"[ScreenSpaceUtility] Invalid dimensions after clamping: {width}x{height}");
return new Rect(Screen.width / 2f, Screen.height / 2f, 100, 100); // Fallback small rect
}
Logging.Debug($"[ScreenSpaceUtility] Clamped bounds - min: ({minX}, {minY}), max: ({maxX}, {maxY})");
if (returnCenterPosition)
{
// Calculate center position from clamped bounds
float centerX = minX + width / 2f;
float centerY = minY + height / 2f;
Logging.Debug($"[ScreenSpaceUtility] Final rect - center: ({centerX}, {centerY}), size: ({width}, {height})");
return new Rect(centerX, centerY, width, height);
}
else
{
// Return bottom-left position
Logging.Debug($"[ScreenSpaceUtility] Final rect - position: ({minX}, {minY}), size: ({width}, {height})");
return new Rect(minX, minY, width, height);
}
}
/// <summary>
/// Check if a screen rect is completely outside screen bounds
/// </summary>
public static bool IsRectOutsideScreenBounds(Rect rect)
{
return rect.xMax < 0 || rect.xMin > Screen.width ||
rect.yMax < 0 || rect.yMin > Screen.height;
}
/// <summary>
/// Get the visible portion of a rect when clamped to screen bounds
/// </summary>
public static Rect GetVisibleScreenRect(Rect rect)
{
float minX = Mathf.Max(0, rect.xMin);
float minY = Mathf.Max(0, rect.yMin);
float maxX = Mathf.Min(Screen.width, rect.xMax);
float maxY = Mathf.Min(Screen.height, rect.yMax);
float width = Mathf.Max(0, maxX - minX);
float height = Mathf.Max(0, maxY - minY);
return new Rect(minX, minY, width, height);
}
/// <summary>
/// Calculate the percentage of a rect that is visible on screen
/// </summary>
public static float GetVisiblePercentage(Rect rect)
{
if (rect.width <= 0 || rect.height <= 0) return 0f;
Rect visibleRect = GetVisibleScreenRect(rect);
float totalArea = rect.width * rect.height;
float visibleArea = visibleRect.width * visibleRect.height;
return visibleArea / totalArea;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f9a38b4740894e6ea8c32d8f62bc5bd6
timeCreated: 1764148562

View File

@@ -1,9 +1,9 @@
using Pixelplacement;
using System;
using Pixelplacement;
using Pixelplacement.TweenSystem;
using UnityEngine;
using System;
namespace Minigames.StatueDressup.Utils
namespace Utils
{
/// <summary>
/// Common animation utilities extracted from CardAnimator pattern.