Add pinch controls to statue dressup game
This commit is contained in:
@@ -228,6 +228,10 @@ namespace AppleHills.Core.Settings
|
||||
bool EnableStatePersistence { get; }
|
||||
string StateSaveKey { get; }
|
||||
int MaxSavedDecorations { get; }
|
||||
|
||||
// Pinch Controls
|
||||
float MinDecorationScale { get; }
|
||||
float MaxDecorationScale { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -90,6 +90,13 @@ namespace Core.Settings
|
||||
[Tooltip("Maximum number of decorations to save")]
|
||||
[SerializeField] private int maxSavedDecorations = 50;
|
||||
|
||||
[Header("Pinch Controls")]
|
||||
[Tooltip("Minimum scale for decorations when using pinch controls")]
|
||||
[SerializeField] private float minDecorationScale = 0.1f;
|
||||
|
||||
[Tooltip("Maximum scale for decorations when using pinch controls")]
|
||||
[SerializeField] private float maxDecorationScale = 2.0f;
|
||||
|
||||
// IStatueDressupSettings implementation - Decoration Display
|
||||
public Vector2 DefaultAuthoredSize => defaultAuthoredSize;
|
||||
|
||||
@@ -136,6 +143,10 @@ namespace Core.Settings
|
||||
public string StateSaveKey => stateSaveKey;
|
||||
public int MaxSavedDecorations => maxSavedDecorations;
|
||||
|
||||
// IStatueDressupSettings implementation - Pinch Controls
|
||||
public float MinDecorationScale => minDecorationScale;
|
||||
public float MaxDecorationScale => maxDecorationScale;
|
||||
|
||||
public override void OnValidate()
|
||||
{
|
||||
base.OnValidate();
|
||||
@@ -168,6 +179,10 @@ namespace Core.Settings
|
||||
|
||||
// Validate state persistence
|
||||
maxSavedDecorations = Mathf.Max(1, maxSavedDecorations);
|
||||
|
||||
// Validate pinch controls
|
||||
minDecorationScale = Mathf.Max(0.01f, minDecorationScale);
|
||||
maxDecorationScale = Mathf.Max(minDecorationScale + 0.1f, maxDecorationScale);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,32 +1,33 @@
|
||||
using Core.SaveLoad;
|
||||
using UnityEditor.Animations;
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
public class FrakkeAnimEventTrigger : MonoBehaviour
|
||||
namespace DamianExperiments.Dump
|
||||
{
|
||||
[Header("Frakke Crashing References")]
|
||||
public AppleMachine FrakkeSMRef;
|
||||
public GameObject stateToChangeToAfterCrashing;
|
||||
|
||||
[Header("Fence Breaking References")]
|
||||
public AppleMachine FenceSMRef;
|
||||
public GameObject FenceStateToSet;
|
||||
|
||||
public void OnFrakkeCrashEnded()
|
||||
public class FrakkeAnimEventTrigger : MonoBehaviour
|
||||
{
|
||||
FrakkeSMRef.ChangeState(stateToChangeToAfterCrashing);
|
||||
}
|
||||
[Header("Frakke Crashing References")]
|
||||
public AppleMachine FrakkeSMRef;
|
||||
public GameObject stateToChangeToAfterCrashing;
|
||||
|
||||
public void OnFenceBroken()
|
||||
{
|
||||
if (FenceSMRef != null)
|
||||
[Header("Fence Breaking References")]
|
||||
public AppleMachine FenceSMRef;
|
||||
public GameObject FenceStateToSet;
|
||||
|
||||
public void OnFrakkeCrashEnded()
|
||||
{
|
||||
FenceSMRef.ChangeState(FenceStateToSet);
|
||||
FrakkeSMRef.ChangeState(stateToChangeToAfterCrashing);
|
||||
}
|
||||
else
|
||||
|
||||
public void OnFenceBroken()
|
||||
{
|
||||
Debug.LogWarning("FrakkeRevUpCrashBehaviour: FenceSMRef is not assigned.");
|
||||
if (FenceSMRef != null)
|
||||
{
|
||||
FenceSMRef.ChangeState(FenceStateToSet);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("FrakkeRevUpCrashBehaviour: FenceSMRef is not assigned.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
using Core.SaveLoad;
|
||||
using UnityEditor.Animations;
|
||||
using UnityEngine;
|
||||
|
||||
public class FrakkeCrashedBehaviour : MonoBehaviour
|
||||
namespace DamianExperiments.Dump
|
||||
{
|
||||
public Animator FrakkeAnimControllerRef;
|
||||
|
||||
|
||||
private void OnEnable()
|
||||
public class FrakkeCrashedBehaviour : MonoBehaviour
|
||||
{
|
||||
if (FrakkeAnimControllerRef != null)
|
||||
{
|
||||
FrakkeAnimControllerRef.SetTrigger("Crashes");
|
||||
}
|
||||
}
|
||||
public Animator FrakkeAnimControllerRef;
|
||||
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
if (FrakkeAnimControllerRef != null)
|
||||
{
|
||||
FrakkeAnimControllerRef.SetTrigger("Crashes");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
using Core.SaveLoad;
|
||||
using UnityEditor.Animations;
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
public class FrakkeRevUpCrashBehaviour : MonoBehaviour
|
||||
namespace DamianExperiments.Dump
|
||||
{
|
||||
public Animator FrakkeAnimControllerRef;
|
||||
|
||||
private void OnEnable()
|
||||
public class FrakkeRevUpCrashBehaviour : MonoBehaviour
|
||||
{
|
||||
if (FrakkeAnimControllerRef != null)
|
||||
public Animator FrakkeAnimControllerRef;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
FrakkeAnimControllerRef.SetTrigger("SpeedsOff");
|
||||
}
|
||||
if (FrakkeAnimControllerRef != null)
|
||||
{
|
||||
FrakkeAnimControllerRef.SetTrigger("SpeedsOff");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -285,7 +285,7 @@ namespace Input
|
||||
};
|
||||
|
||||
var results = new System.Collections.Generic.List<RaycastResult>();
|
||||
EventSystem.current.RaycastAll(eventData, results);
|
||||
EventSystem.current.RaycastAll(eventData, results);
|
||||
|
||||
foreach (var result in results)
|
||||
{
|
||||
|
||||
@@ -26,9 +26,8 @@ namespace Minigames.StatueDressup.Controllers
|
||||
[SerializeField] private GameObject statue;
|
||||
[SerializeField] private DecorationDraggableInstance draggablePrefab; // Prefab for spawning decorations
|
||||
|
||||
[Header("Edit UI")]
|
||||
[SerializeField] private UI.DecorationEditUI editUIPrefab; // Prefab for edit UI
|
||||
private UI.DecorationEditUI _editUIInstance;
|
||||
[Header("Pinch Controls")]
|
||||
private UI.DecorationPinchController _pinchControllerInstance;
|
||||
|
||||
[Header("UI Pages")]
|
||||
[SerializeField] private UI.PlayAreaPage playAreaPage;
|
||||
@@ -460,35 +459,37 @@ namespace Minigames.StatueDressup.Controllers
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show edit UI for a placed decoration
|
||||
/// Show pinch controls for a placed decoration
|
||||
/// </summary>
|
||||
public void ShowEditUI(DecorationDraggableInstance decoration)
|
||||
{
|
||||
if (decoration == null)
|
||||
{
|
||||
Logging.Warning("[StatueDecorationController] Cannot show edit UI - decoration is null");
|
||||
Logging.Warning("[StatueDecorationController] Cannot show pinch controls - decoration is null");
|
||||
return;
|
||||
}
|
||||
|
||||
// Create edit UI instance if needed
|
||||
if (_editUIInstance == null)
|
||||
// Create pinch controller instance if needed
|
||||
if (_pinchControllerInstance == null)
|
||||
{
|
||||
if (editUIPrefab == null)
|
||||
{
|
||||
Logging.Error("[StatueDecorationController] Edit UI prefab is not assigned!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Instantiate as child of canvas (find appropriate parent)
|
||||
// Find canvas transform
|
||||
Transform canvasTransform = statueArea != null ? statueArea.root : transform.root;
|
||||
_editUIInstance = Instantiate(editUIPrefab, canvasTransform);
|
||||
_editUIInstance.transform.SetAsLastSibling(); // Ensure it's on top
|
||||
|
||||
Logging.Debug("[StatueDecorationController] Created edit UI instance");
|
||||
// Create new GameObject with DecorationPinchController component
|
||||
GameObject pinchControllerObj = new GameObject("DecorationPinchController");
|
||||
pinchControllerObj.transform.SetParent(canvasTransform, false);
|
||||
|
||||
// Add the component (it will auto-create RectTransform, Image, CanvasGroup, PinchGestureHandler in Awake)
|
||||
_pinchControllerInstance = pinchControllerObj.AddComponent<UI.DecorationPinchController>();
|
||||
|
||||
// Ensure it's on top
|
||||
pinchControllerObj.transform.SetAsLastSibling();
|
||||
|
||||
Logging.Debug("[StatueDecorationController] Created pinch controller instance dynamically");
|
||||
}
|
||||
|
||||
// Show the UI
|
||||
_editUIInstance.Show(decoration);
|
||||
// Show the pinch controls
|
||||
_pinchControllerInstance.Show(decoration);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,288 +0,0 @@
|
||||
using Core;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using Minigames.StatueDressup.DragDrop;
|
||||
|
||||
namespace Minigames.StatueDressup.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// UI panel for editing a placed decoration's scale and rotation.
|
||||
/// Shows sliders for scale (0.1x - 2x) and rotation (-180° to 180°).
|
||||
/// Changes are applied immediately to the decoration's transform.
|
||||
/// </summary>
|
||||
public class DecorationEditUI : MonoBehaviour
|
||||
{
|
||||
[Header("UI References")]
|
||||
[SerializeField] private Slider scaleSlider;
|
||||
[SerializeField] private Slider rotationSlider;
|
||||
[SerializeField] private Button confirmButton;
|
||||
[SerializeField] private Button resetButton;
|
||||
[SerializeField] private CanvasGroup canvasGroup;
|
||||
|
||||
[Header("Slider Ranges")]
|
||||
[SerializeField] private float minScale = 0.1f;
|
||||
[SerializeField] private float maxScale = 2.0f;
|
||||
[SerializeField] private float minRotation = -180f;
|
||||
[SerializeField] private float maxRotation = 180f;
|
||||
|
||||
private DecorationDraggableInstance _targetDecoration;
|
||||
private RectTransform _rectTransform;
|
||||
private float _originalRotation;
|
||||
private bool _isInitialized;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
// Get RectTransform
|
||||
_rectTransform = GetComponent<RectTransform>();
|
||||
|
||||
// Ensure canvas group exists
|
||||
if (canvasGroup == null)
|
||||
{
|
||||
canvasGroup = GetComponent<CanvasGroup>();
|
||||
if (canvasGroup == null)
|
||||
{
|
||||
canvasGroup = gameObject.AddComponent<CanvasGroup>();
|
||||
}
|
||||
}
|
||||
|
||||
// Setup slider ranges
|
||||
if (scaleSlider != null)
|
||||
{
|
||||
scaleSlider.minValue = minScale;
|
||||
scaleSlider.maxValue = maxScale;
|
||||
scaleSlider.onValueChanged.AddListener(OnScaleChanged);
|
||||
}
|
||||
|
||||
if (rotationSlider != null)
|
||||
{
|
||||
rotationSlider.minValue = minRotation;
|
||||
rotationSlider.maxValue = maxRotation;
|
||||
rotationSlider.onValueChanged.AddListener(OnRotationChanged);
|
||||
}
|
||||
|
||||
// Setup buttons
|
||||
if (confirmButton != null)
|
||||
{
|
||||
confirmButton.onClick.AddListener(OnConfirm);
|
||||
}
|
||||
|
||||
if (resetButton != null)
|
||||
{
|
||||
resetButton.onClick.AddListener(OnReset);
|
||||
}
|
||||
|
||||
// Start hidden
|
||||
gameObject.SetActive(false);
|
||||
|
||||
_isInitialized = true;
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
// Clean up listeners
|
||||
if (scaleSlider != null)
|
||||
{
|
||||
scaleSlider.onValueChanged.RemoveListener(OnScaleChanged);
|
||||
}
|
||||
|
||||
if (rotationSlider != null)
|
||||
{
|
||||
rotationSlider.onValueChanged.RemoveListener(OnRotationChanged);
|
||||
}
|
||||
|
||||
if (confirmButton != null)
|
||||
{
|
||||
confirmButton.onClick.RemoveListener(OnConfirm);
|
||||
}
|
||||
|
||||
if (resetButton != null)
|
||||
{
|
||||
resetButton.onClick.RemoveListener(OnReset);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show the edit UI for the given decoration
|
||||
/// </summary>
|
||||
public void Show(DecorationDraggableInstance decoration)
|
||||
{
|
||||
if (!_isInitialized)
|
||||
{
|
||||
Logging.Error("[DecorationEditUI] Attempted to show before initialization!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (decoration == null)
|
||||
{
|
||||
Logging.Error("[DecorationEditUI] Cannot show edit UI - decoration is null!");
|
||||
return;
|
||||
}
|
||||
|
||||
_targetDecoration = decoration;
|
||||
|
||||
// Store original rotation for reference
|
||||
_originalRotation = decoration.transform.localEulerAngles.z;
|
||||
|
||||
// Normalize rotation to -180 to 180 range
|
||||
if (_originalRotation > 180f)
|
||||
{
|
||||
_originalRotation -= 360f;
|
||||
}
|
||||
|
||||
// Initialize sliders from current transform values
|
||||
if (scaleSlider != null)
|
||||
{
|
||||
// Use X component for uniform scale
|
||||
float currentScale = decoration.transform.localScale.x;
|
||||
scaleSlider.value = Mathf.Clamp(currentScale, minScale, maxScale);
|
||||
}
|
||||
|
||||
if (rotationSlider != null)
|
||||
{
|
||||
rotationSlider.value = Mathf.Clamp(_originalRotation, minRotation, maxRotation);
|
||||
}
|
||||
|
||||
// Disable decoration raycasts during editing
|
||||
CanvasGroup decorationCanvasGroup = decoration.GetComponent<CanvasGroup>();
|
||||
if (decorationCanvasGroup != null)
|
||||
{
|
||||
decorationCanvasGroup.blocksRaycasts = false;
|
||||
}
|
||||
|
||||
// Position UI centered over the decoration (context menu style)
|
||||
PositionOverDecoration(decoration);
|
||||
|
||||
// Show UI immediately
|
||||
gameObject.SetActive(true);
|
||||
|
||||
if (canvasGroup != null)
|
||||
{
|
||||
canvasGroup.alpha = 1f;
|
||||
}
|
||||
|
||||
Logging.Debug($"[DecorationEditUI] Showing edit UI for: {decoration.Data?.DecorationName}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Position the UI centered over the decoration (context menu style)
|
||||
/// </summary>
|
||||
private void PositionOverDecoration(DecorationDraggableInstance decoration)
|
||||
{
|
||||
if (_rectTransform == null || decoration == null) return;
|
||||
|
||||
// Get decoration's world position
|
||||
Vector3 decorationWorldPos = decoration.transform.position;
|
||||
|
||||
// Convert to canvas space if using screen space overlay
|
||||
Canvas canvas = GetComponentInParent<Canvas>();
|
||||
if (canvas != null && canvas.renderMode == RenderMode.ScreenSpaceOverlay)
|
||||
{
|
||||
// For overlay canvas, world position is already correct
|
||||
_rectTransform.position = decorationWorldPos;
|
||||
}
|
||||
else if (canvas != null)
|
||||
{
|
||||
// For other canvas modes, convert properly
|
||||
RectTransformUtility.ScreenPointToLocalPointInRectangle(
|
||||
canvas.transform as RectTransform,
|
||||
RectTransformUtility.WorldToScreenPoint(canvas.worldCamera, decorationWorldPos),
|
||||
canvas.worldCamera,
|
||||
out Vector2 localPoint
|
||||
);
|
||||
_rectTransform.localPosition = localPoint;
|
||||
}
|
||||
|
||||
Logging.Debug($"[DecorationEditUI] Positioned at decoration location: {decorationWorldPos}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hide the edit UI
|
||||
/// </summary>
|
||||
public void Hide()
|
||||
{
|
||||
if (_targetDecoration != null)
|
||||
{
|
||||
// Re-enable decoration raycasts
|
||||
CanvasGroup decorationCanvasGroup = _targetDecoration.GetComponent<CanvasGroup>();
|
||||
if (decorationCanvasGroup != null)
|
||||
{
|
||||
decorationCanvasGroup.blocksRaycasts = true;
|
||||
}
|
||||
}
|
||||
|
||||
_targetDecoration = null;
|
||||
gameObject.SetActive(false);
|
||||
|
||||
Logging.Debug("[DecorationEditUI] Edit UI hidden");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle scale slider change
|
||||
/// </summary>
|
||||
private void OnScaleChanged(float value)
|
||||
{
|
||||
if (_targetDecoration == null) return;
|
||||
|
||||
// Apply uniform scale (X, Y, Z all the same)
|
||||
_targetDecoration.transform.localScale = Vector3.one * value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle rotation slider change
|
||||
/// </summary>
|
||||
private void OnRotationChanged(float value)
|
||||
{
|
||||
if (_targetDecoration == null) return;
|
||||
|
||||
// Apply Z rotation only
|
||||
_targetDecoration.transform.localEulerAngles = new Vector3(0f, 0f, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle confirm button - save changes and close
|
||||
/// </summary>
|
||||
private void OnConfirm()
|
||||
{
|
||||
if (_targetDecoration != null)
|
||||
{
|
||||
// Trigger auto-save through the controller
|
||||
var controller = Controllers.StatueDecorationController.Instance;
|
||||
if (controller != null)
|
||||
{
|
||||
// The controller's RegisterDecoration already triggers SaveStatueState
|
||||
// Since the decoration is already registered, we just need to trigger a save
|
||||
// This happens automatically on the next RegisterDecoration/UnregisterDecoration call
|
||||
Logging.Debug("[DecorationEditUI] Changes confirmed - will be auto-saved");
|
||||
}
|
||||
}
|
||||
|
||||
Hide();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle reset button - restore original values
|
||||
/// </summary>
|
||||
private void OnReset()
|
||||
{
|
||||
if (_targetDecoration == null) return;
|
||||
|
||||
// Reset to authored size (scale 1.0) and 0 rotation
|
||||
float defaultScale = 1.0f;
|
||||
float defaultRotation = 0f;
|
||||
|
||||
if (scaleSlider != null)
|
||||
{
|
||||
scaleSlider.value = defaultScale;
|
||||
}
|
||||
|
||||
if (rotationSlider != null)
|
||||
{
|
||||
rotationSlider.value = defaultRotation;
|
||||
}
|
||||
|
||||
// Values are applied through the slider callbacks
|
||||
Logging.Debug("[DecorationEditUI] Reset to defaults");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7f3e2a1b9c4d5e6f7a8b9c0d1e2f3a4b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@@ -0,0 +1,266 @@
|
||||
using Core;
|
||||
using Minigames.StatueDressup.Controllers;
|
||||
using Minigames.StatueDressup.DragDrop;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Minigames.StatueDressup.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// Manages pinch-to-scale and pinch-to-rotate controls for placed decorations.
|
||||
/// Shows a semi-transparent backdrop and listens for pinch gestures.
|
||||
/// Tap anywhere to dismiss and save changes.
|
||||
/// </summary>
|
||||
public class DecorationPinchController : MonoBehaviour
|
||||
{
|
||||
[Header("UI References")]
|
||||
[SerializeField] private PinchGestureHandler gestureHandler;
|
||||
[SerializeField] private CanvasGroup backdropCanvasGroup;
|
||||
[SerializeField] private Image backdropImage;
|
||||
|
||||
[Header("Backdrop Settings")]
|
||||
[SerializeField] private Color backdropColor = new Color(0f, 0f, 0f, 0.5f);
|
||||
[Tooltip("Sort order for the active decoration to render above backdrop")]
|
||||
[SerializeField] private int activeDecorationSortOrder = 100;
|
||||
|
||||
private DecorationDraggableInstance _targetDecoration;
|
||||
private AppleHills.Core.Settings.IStatueDressupSettings _settings;
|
||||
private bool _isInitialized;
|
||||
|
||||
// Canvas management for rendering order
|
||||
private Canvas _temporaryDecorationCanvas;
|
||||
private int _backdropSortOrder;
|
||||
private void Awake()
|
||||
{
|
||||
// Ensure components exist
|
||||
if (gestureHandler == null)
|
||||
{
|
||||
gestureHandler = GetComponentInChildren<PinchGestureHandler>();
|
||||
if (gestureHandler == null)
|
||||
{
|
||||
// Create gesture handler if not found
|
||||
GameObject handlerObj = new GameObject("PinchGestureHandler");
|
||||
handlerObj.transform.SetParent(transform, false);
|
||||
gestureHandler = handlerObj.AddComponent<PinchGestureHandler>();
|
||||
}
|
||||
}
|
||||
|
||||
if (backdropCanvasGroup == null)
|
||||
{
|
||||
backdropCanvasGroup = GetComponent<CanvasGroup>();
|
||||
if (backdropCanvasGroup == null)
|
||||
{
|
||||
backdropCanvasGroup = gameObject.AddComponent<CanvasGroup>();
|
||||
}
|
||||
}
|
||||
|
||||
if (backdropImage == null)
|
||||
{
|
||||
backdropImage = GetComponent<Image>();
|
||||
if (backdropImage == null)
|
||||
{
|
||||
backdropImage = gameObject.AddComponent<Image>();
|
||||
}
|
||||
}
|
||||
|
||||
// Configure backdrop
|
||||
backdropImage.color = backdropColor;
|
||||
backdropImage.raycastTarget = true;
|
||||
|
||||
// Ensure fullscreen
|
||||
RectTransform rectTransform = GetComponent<RectTransform>();
|
||||
if (rectTransform != null)
|
||||
{
|
||||
rectTransform.anchorMin = Vector2.zero;
|
||||
rectTransform.anchorMax = Vector2.one;
|
||||
rectTransform.offsetMin = Vector2.zero;
|
||||
rectTransform.offsetMax = Vector2.zero;
|
||||
}
|
||||
|
||||
// Subscribe to gesture events
|
||||
if (gestureHandler != null)
|
||||
{
|
||||
gestureHandler.OnPinchScale += HandlePinchScale;
|
||||
gestureHandler.OnPinchRotate += HandlePinchRotate;
|
||||
gestureHandler.OnDismissTap += HandleDismiss;
|
||||
}
|
||||
|
||||
// Load settings
|
||||
_settings = DecorationDataManager.Instance?.Settings;
|
||||
|
||||
// Get backdrop's canvas sort order for reference
|
||||
Canvas backdropCanvas = GetComponentInParent<Canvas>();
|
||||
if (backdropCanvas != null)
|
||||
{
|
||||
_backdropSortOrder = backdropCanvas.sortingOrder;
|
||||
}
|
||||
|
||||
// Start hidden
|
||||
gameObject.SetActive(false);
|
||||
|
||||
_isInitialized = true;
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
// Unsubscribe from gesture events
|
||||
if (gestureHandler != null)
|
||||
{
|
||||
gestureHandler.OnPinchScale -= HandlePinchScale;
|
||||
gestureHandler.OnPinchRotate -= HandlePinchRotate;
|
||||
gestureHandler.OnDismissTap -= HandleDismiss;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show pinch controls for the given decoration
|
||||
/// </summary>
|
||||
public void Show(DecorationDraggableInstance decoration)
|
||||
{
|
||||
if (!_isInitialized)
|
||||
{
|
||||
Logging.Error("[DecorationPinchController] Attempted to show before initialization!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (decoration == null)
|
||||
{
|
||||
Logging.Error("[DecorationPinchController] Cannot show pinch controller - decoration is null!");
|
||||
return;
|
||||
}
|
||||
|
||||
_targetDecoration = decoration;
|
||||
|
||||
// Add temporary Canvas component to decoration to control render order
|
||||
_temporaryDecorationCanvas = decoration.gameObject.GetComponent<Canvas>();
|
||||
if (_temporaryDecorationCanvas == null)
|
||||
{
|
||||
_temporaryDecorationCanvas = decoration.gameObject.AddComponent<Canvas>();
|
||||
}
|
||||
|
||||
// Configure canvas to override sorting and render above backdrop
|
||||
_temporaryDecorationCanvas.overrideSorting = true;
|
||||
_temporaryDecorationCanvas.sortingOrder = _backdropSortOrder + activeDecorationSortOrder;
|
||||
|
||||
// Disable decoration raycasts during editing
|
||||
CanvasGroup decorationCanvasGroup = decoration.GetComponent<CanvasGroup>();
|
||||
if (decorationCanvasGroup != null)
|
||||
{
|
||||
decorationCanvasGroup.blocksRaycasts = false;
|
||||
}
|
||||
|
||||
// Show UI
|
||||
gameObject.SetActive(true);
|
||||
|
||||
if (backdropCanvasGroup != null)
|
||||
{
|
||||
backdropCanvasGroup.alpha = 1f;
|
||||
backdropCanvasGroup.blocksRaycasts = true;
|
||||
}
|
||||
|
||||
// Activate gesture detection
|
||||
if (gestureHandler != null)
|
||||
{
|
||||
gestureHandler.Activate();
|
||||
}
|
||||
|
||||
Logging.Debug($"[DecorationPinchController] Showing pinch controls for: {decoration.Data?.DecorationName}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hide pinch controls
|
||||
/// </summary>
|
||||
public void Hide()
|
||||
{
|
||||
if (_targetDecoration != null)
|
||||
{
|
||||
// Remove temporary Canvas component to restore original rendering
|
||||
if (_temporaryDecorationCanvas != null)
|
||||
{
|
||||
Destroy(_temporaryDecorationCanvas);
|
||||
_temporaryDecorationCanvas = null;
|
||||
}
|
||||
|
||||
// Re-enable decoration raycasts
|
||||
CanvasGroup decorationCanvasGroup = _targetDecoration.GetComponent<CanvasGroup>();
|
||||
if (decorationCanvasGroup != null)
|
||||
{
|
||||
decorationCanvasGroup.blocksRaycasts = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Deactivate gesture detection
|
||||
if (gestureHandler != null)
|
||||
{
|
||||
gestureHandler.Deactivate();
|
||||
}
|
||||
|
||||
_targetDecoration = null;
|
||||
gameObject.SetActive(false);
|
||||
|
||||
Logging.Debug("[DecorationPinchController] Pinch controls hidden");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle pinch scale gesture
|
||||
/// </summary>
|
||||
private void HandlePinchScale(float scaleDelta)
|
||||
{
|
||||
if (_targetDecoration == null || _settings == null) return;
|
||||
|
||||
// Get current scale
|
||||
Vector3 currentScale = _targetDecoration.transform.localScale;
|
||||
float newScaleValue = currentScale.x + scaleDelta;
|
||||
|
||||
// Clamp to settings constraints
|
||||
newScaleValue = Mathf.Clamp(newScaleValue, _settings.MinDecorationScale, _settings.MaxDecorationScale);
|
||||
|
||||
// Apply uniform scale
|
||||
_targetDecoration.transform.localScale = Vector3.one * newScaleValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle pinch rotate gesture
|
||||
/// </summary>
|
||||
private void HandlePinchRotate(float angleDelta)
|
||||
{
|
||||
if (_targetDecoration == null) return;
|
||||
|
||||
// Get current rotation
|
||||
Vector3 currentRotation = _targetDecoration.transform.localEulerAngles;
|
||||
|
||||
// Apply rotation delta (Z-axis only for 2D)
|
||||
float newZRotation = currentRotation.z + angleDelta;
|
||||
|
||||
// Normalize to -180 to 180 range for cleaner values
|
||||
while (newZRotation > 180f) newZRotation -= 360f;
|
||||
while (newZRotation < -180f) newZRotation += 360f;
|
||||
|
||||
_targetDecoration.transform.localEulerAngles = new Vector3(0f, 0f, newZRotation);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle dismiss tap - save and close
|
||||
/// </summary>
|
||||
private void HandleDismiss()
|
||||
{
|
||||
if (_targetDecoration != null)
|
||||
{
|
||||
// Trigger auto-save through the controller
|
||||
var controller = Controllers.StatueDecorationController.Instance;
|
||||
if (controller != null)
|
||||
{
|
||||
// The decoration is already registered, but we need to trigger a save
|
||||
// We can force this by unregistering and re-registering
|
||||
controller.UnregisterDecoration(_targetDecoration);
|
||||
controller.RegisterDecoration(_targetDecoration);
|
||||
|
||||
Logging.Debug("[DecorationPinchController] Changes saved on dismissal");
|
||||
}
|
||||
}
|
||||
|
||||
Hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1c8a81e283ec41b7b257c0a6824d32cf
|
||||
timeCreated: 1765369467
|
||||
212
Assets/Scripts/Minigames/StatueDressup/UI/PinchGestureHandler.cs
Normal file
212
Assets/Scripts/Minigames/StatueDressup/UI/PinchGestureHandler.cs
Normal file
@@ -0,0 +1,212 @@
|
||||
using System;
|
||||
using Core;
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
using UnityEngine.InputSystem.EnhancedTouch;
|
||||
using Touch = UnityEngine.InputSystem.EnhancedTouch.Touch;
|
||||
|
||||
namespace Minigames.StatueDressup.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// Detects and processes pinch gestures for scaling and rotation using Unity's new Input System (EnhancedTouch).
|
||||
/// Also detects single-tap dismissal and provides keyboard shortcuts for editor testing.
|
||||
/// </summary>
|
||||
public class PinchGestureHandler : MonoBehaviour
|
||||
{
|
||||
[Header("Gesture Settings")]
|
||||
[Tooltip("Minimum distance change to register as pinch scale gesture")]
|
||||
[SerializeField] private float minPinchDistance = 10f;
|
||||
|
||||
[Tooltip("Sensitivity multiplier for scale changes")]
|
||||
[SerializeField] private float scaleSensitivity = 0.01f;
|
||||
|
||||
[Tooltip("Sensitivity multiplier for rotation changes")]
|
||||
[SerializeField] private float rotationSensitivity = 1.0f;
|
||||
|
||||
[Header("Editor Testing (Keyboard)")]
|
||||
[Tooltip("Keyboard rotation speed in degrees per second")]
|
||||
[SerializeField] private float keyboardRotationSpeed = 90f;
|
||||
|
||||
[Tooltip("Keyboard scale speed per second")]
|
||||
[SerializeField] private float keyboardScaleSpeed = 0.5f;
|
||||
|
||||
// Events
|
||||
public event Action<float> OnPinchScale;
|
||||
public event Action<float> OnPinchRotate;
|
||||
public event Action OnDismissTap;
|
||||
|
||||
private bool _isActive;
|
||||
private float _previousDistance;
|
||||
private float _previousAngle;
|
||||
private double _touchStartTime;
|
||||
private bool _wasTouchTracked;
|
||||
|
||||
/// <summary>
|
||||
/// Activate gesture detection
|
||||
/// </summary>
|
||||
public void Activate()
|
||||
{
|
||||
_isActive = true;
|
||||
_previousDistance = 0f;
|
||||
_previousAngle = 0f;
|
||||
_wasTouchTracked = false;
|
||||
|
||||
// Enable enhanced touch support for new Input System
|
||||
if (!EnhancedTouchSupport.enabled)
|
||||
{
|
||||
EnhancedTouchSupport.Enable();
|
||||
}
|
||||
|
||||
Logging.Debug("[PinchGestureHandler] Activated");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deactivate gesture detection
|
||||
/// </summary>
|
||||
public void Deactivate()
|
||||
{
|
||||
_isActive = false;
|
||||
Logging.Debug("[PinchGestureHandler] Deactivated");
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!_isActive) return;
|
||||
|
||||
// Handle touch input (mobile) - using new Input System
|
||||
if (Touch.activeTouches.Count > 0)
|
||||
{
|
||||
HandleTouchInput();
|
||||
}
|
||||
// Handle keyboard input (editor testing)
|
||||
else if (Application.isEditor)
|
||||
{
|
||||
HandleKeyboardInput();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process touch input for pinch gestures and single-tap dismissal
|
||||
/// </summary>
|
||||
private void HandleTouchInput()
|
||||
{
|
||||
var activeTouches = Touch.activeTouches;
|
||||
|
||||
// Single tap for dismissal
|
||||
if (activeTouches.Count == 1)
|
||||
{
|
||||
Touch touch = activeTouches[0];
|
||||
|
||||
// Track touch start time
|
||||
if (touch.phase == UnityEngine.InputSystem.TouchPhase.Began)
|
||||
{
|
||||
_touchStartTime = touch.startTime;
|
||||
_wasTouchTracked = true;
|
||||
}
|
||||
// Detect tap (touch began and ended quickly - within 0.3 seconds)
|
||||
else if (touch.phase == UnityEngine.InputSystem.TouchPhase.Ended && _wasTouchTracked)
|
||||
{
|
||||
double touchDuration = Time.timeAsDouble - _touchStartTime;
|
||||
if (touchDuration < 0.3)
|
||||
{
|
||||
Logging.Debug("[PinchGestureHandler] Single tap detected for dismissal");
|
||||
OnDismissTap?.Invoke();
|
||||
}
|
||||
_wasTouchTracked = false;
|
||||
}
|
||||
}
|
||||
// Two-finger pinch for scale and rotation
|
||||
else if (activeTouches.Count == 2)
|
||||
{
|
||||
Touch touch0 = activeTouches[0];
|
||||
Touch touch1 = activeTouches[1];
|
||||
|
||||
// Calculate current positions and distance
|
||||
Vector2 currentTouch0 = touch0.screenPosition;
|
||||
Vector2 currentTouch1 = touch1.screenPosition;
|
||||
float currentDistance = Vector2.Distance(currentTouch0, currentTouch1);
|
||||
float currentAngle = Mathf.Atan2(currentTouch1.y - currentTouch0.y, currentTouch1.x - currentTouch0.x) * Mathf.Rad2Deg;
|
||||
|
||||
// Initialize on first frame of pinch
|
||||
if (touch0.phase == UnityEngine.InputSystem.TouchPhase.Began || touch1.phase == UnityEngine.InputSystem.TouchPhase.Began)
|
||||
{
|
||||
_previousDistance = currentDistance;
|
||||
_previousAngle = currentAngle;
|
||||
Logging.Debug("[PinchGestureHandler] Pinch gesture started");
|
||||
return;
|
||||
}
|
||||
|
||||
// Process pinch scale (distance change)
|
||||
if (touch0.phase == UnityEngine.InputSystem.TouchPhase.Moved || touch1.phase == UnityEngine.InputSystem.TouchPhase.Moved)
|
||||
{
|
||||
if (_previousDistance > minPinchDistance)
|
||||
{
|
||||
float distanceDelta = currentDistance - _previousDistance;
|
||||
float scaleDelta = distanceDelta * scaleSensitivity;
|
||||
|
||||
if (Mathf.Abs(scaleDelta) > 0.001f)
|
||||
{
|
||||
OnPinchScale?.Invoke(scaleDelta);
|
||||
Logging.Debug($"[PinchGestureHandler] Scale delta: {scaleDelta:F3}");
|
||||
}
|
||||
}
|
||||
|
||||
// Process pinch rotation (angle change)
|
||||
float angleDelta = Mathf.DeltaAngle(_previousAngle, currentAngle) * rotationSensitivity;
|
||||
|
||||
if (Mathf.Abs(angleDelta) > 0.5f)
|
||||
{
|
||||
OnPinchRotate?.Invoke(angleDelta);
|
||||
Logging.Debug($"[PinchGestureHandler] Rotation delta: {angleDelta:F1}°");
|
||||
}
|
||||
|
||||
// Update previous values
|
||||
_previousDistance = currentDistance;
|
||||
_previousAngle = currentAngle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process keyboard input for editor testing
|
||||
/// Q/E for rotation, +/- for scale, Escape for dismissal
|
||||
/// </summary>
|
||||
private void HandleKeyboardInput()
|
||||
{
|
||||
var keyboard = Keyboard.current;
|
||||
if (keyboard == null) return;
|
||||
|
||||
// Rotation with Q/E
|
||||
if (keyboard.qKey.isPressed)
|
||||
{
|
||||
float rotateDelta = -keyboardRotationSpeed * Time.deltaTime;
|
||||
OnPinchRotate?.Invoke(rotateDelta);
|
||||
}
|
||||
else if (keyboard.eKey.isPressed)
|
||||
{
|
||||
float rotateDelta = keyboardRotationSpeed * Time.deltaTime;
|
||||
OnPinchRotate?.Invoke(rotateDelta);
|
||||
}
|
||||
|
||||
// Scale with +/- (equals key is where + is on keyboard)
|
||||
if (keyboard.equalsKey.isPressed || keyboard.numpadPlusKey.isPressed)
|
||||
{
|
||||
float scaleDelta = keyboardScaleSpeed * Time.deltaTime;
|
||||
OnPinchScale?.Invoke(scaleDelta);
|
||||
}
|
||||
else if (keyboard.minusKey.isPressed || keyboard.numpadMinusKey.isPressed)
|
||||
{
|
||||
float scaleDelta = -keyboardScaleSpeed * Time.deltaTime;
|
||||
OnPinchScale?.Invoke(scaleDelta);
|
||||
}
|
||||
|
||||
// Dismissal with Escape or Space
|
||||
if (keyboard.escapeKey.wasPressedThisFrame || keyboard.spaceKey.wasPressedThisFrame)
|
||||
{
|
||||
Logging.Debug("[PinchGestureHandler] Keyboard dismissal detected");
|
||||
OnDismissTap?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: daa669f49a29434a927eafbd291e251f
|
||||
timeCreated: 1765369442
|
||||
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3a935f5e791c46df8920c2c33f1c24c0
|
||||
timeCreated: 1765361215
|
||||
@@ -1,99 +0,0 @@
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using Minigames.TrashMaze.Objects;
|
||||
|
||||
namespace Minigames.TrashMaze.Editor
|
||||
{
|
||||
[CustomEditor(typeof(RevealableObject))]
|
||||
public class RevealableObjectEditor : UnityEditor.Editor
|
||||
{
|
||||
private RenderTexture _cachedStampTexture;
|
||||
private Texture2D _previewTexture;
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
DrawDefaultInspector();
|
||||
|
||||
// Only show debug info in play mode
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EditorGUILayout.Space(10);
|
||||
EditorGUILayout.LabelField("Progressive Reveal Debug (Play Mode Only)", EditorStyles.boldLabel);
|
||||
EditorGUILayout.HelpBox("Shows the current stamp texture for Progressive reveal mode.", MessageType.Info);
|
||||
|
||||
RevealableObject revealableObject = (RevealableObject)target;
|
||||
|
||||
// Use reflection to get private _revealStampTexture field
|
||||
var field = typeof(RevealableObject).GetField("_revealStampTexture",
|
||||
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||||
|
||||
if (field != null)
|
||||
{
|
||||
RenderTexture stampTexture = field.GetValue(revealableObject) as RenderTexture;
|
||||
|
||||
if (stampTexture != null)
|
||||
{
|
||||
// Display stamp texture info
|
||||
EditorGUILayout.LabelField("Stamp Texture:", $"{stampTexture.width}x{stampTexture.height}");
|
||||
|
||||
// Show preview of stamp texture
|
||||
GUILayout.Label("Reveal Mask Preview (White = Revealed):");
|
||||
|
||||
// Create preview texture if needed
|
||||
if (_cachedStampTexture != stampTexture || _previewTexture == null)
|
||||
{
|
||||
_cachedStampTexture = stampTexture;
|
||||
|
||||
if (_previewTexture != null)
|
||||
{
|
||||
DestroyImmediate(_previewTexture);
|
||||
}
|
||||
|
||||
_previewTexture = new Texture2D(stampTexture.width, stampTexture.height, TextureFormat.R8, false);
|
||||
_previewTexture.filterMode = FilterMode.Point;
|
||||
}
|
||||
|
||||
// Copy RenderTexture to Texture2D for preview
|
||||
RenderTexture.active = stampTexture;
|
||||
_previewTexture.ReadPixels(new Rect(0, 0, stampTexture.width, stampTexture.height), 0, 0);
|
||||
_previewTexture.Apply();
|
||||
RenderTexture.active = null;
|
||||
|
||||
// Display preview with fixed size
|
||||
float previewSize = 256f;
|
||||
float aspectRatio = (float)stampTexture.height / stampTexture.width;
|
||||
Rect previewRect = GUILayoutUtility.GetRect(previewSize, previewSize * aspectRatio);
|
||||
EditorGUI.DrawPreviewTexture(previewRect, _previewTexture, null, ScaleMode.ScaleToFit);
|
||||
|
||||
// Auto-refresh in play mode
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
Repaint();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUILayout.HelpBox("No stamp texture found. Make sure object is in Progressive reveal mode.", MessageType.Warning);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUILayout.HelpBox("Could not access stamp texture via reflection.", MessageType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
// Clean up preview texture
|
||||
if (_previewTexture != null)
|
||||
{
|
||||
DestroyImmediate(_previewTexture);
|
||||
_previewTexture = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1d081993ee424269bf8eae99db36a54c
|
||||
timeCreated: 1765361215
|
||||
Reference in New Issue
Block a user