Add pinch controls to statue dressup game

This commit is contained in:
Michal Pikulski
2025-12-10 15:15:11 +01:00
parent 082ce98f79
commit 0b4def8a05
21 changed files with 599 additions and 1487 deletions

View File

@@ -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>

View File

@@ -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);
}
}
}

View File

@@ -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.");
}
}
}
}

View File

@@ -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");
}
}
}
}

View File

@@ -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");
}
}
}
}

View File

@@ -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)
{

View File

@@ -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>

View File

@@ -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");
}
}
}

View File

@@ -1,12 +0,0 @@
fileFormatVersion: 2
guid: 7f3e2a1b9c4d5e6f7a8b9c0d1e2f3a4b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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();
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 1c8a81e283ec41b7b257c0a6824d32cf
timeCreated: 1765369467

View 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();
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: daa669f49a29434a927eafbd291e251f
timeCreated: 1765369442

View File

@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 3a935f5e791c46df8920c2c33f1c24c0
timeCreated: 1765361215

View File

@@ -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;
}
}
}
}

View File

@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 1d081993ee424269bf8eae99db36a54c
timeCreated: 1765361215