Update the photo taking mode to automatic and hone in visual feedback

This commit is contained in:
Michal Pikulski
2025-12-16 21:31:06 +01:00
parent ecb9bf1b2b
commit 4063e66362
15 changed files with 1581 additions and 99 deletions

View File

@@ -1,7 +1,7 @@
using Core.Settings;
using AppleHills.Core.Settings;
using UnityEngine;
namespace AppleHills.Core.Settings
namespace Core.Settings
{
/// <summary>
/// Settings related to minigames
@@ -139,6 +139,12 @@ namespace AppleHills.Core.Settings
[Header("Photo Input")]
[Tooltip("Input mode for photo taking sequence")]
[SerializeField] private PhotoInputModes photoInputMode = PhotoInputModes.Tap;
[Tooltip("Photo taking mode - whether photo sequence is triggered by input or automatically")]
[SerializeField] private PhotoTakingMode photoTakingMode = PhotoTakingMode.Input;
[Tooltip("Delay before automatic photo when PhotoTakingMode is Automatic (in seconds)")]
[SerializeField] private float autoPhotoDelay = 0.5f;
[Tooltip("Padding factor to add space around the monster (1.0 = exact size, 1.2 = 20% extra)")]
[SerializeField] private float paddingFactor = 1.2f;
@@ -222,6 +228,8 @@ namespace AppleHills.Core.Settings
public float MaxSizePercent => maxSizePercent;
public float MinSizePercent => minSizePercent;
public PhotoInputModes PhotoInputMode => photoInputMode;
public PhotoTakingMode PhotoTakingMode => photoTakingMode;
public float AutoPhotoDelay => autoPhotoDelay;
public override void OnValidate()
{
@@ -294,6 +302,9 @@ namespace AppleHills.Core.Settings
{
viewfinderProgressThresholds[i] = Mathf.Clamp01(viewfinderProgressThresholds[i]);
}
// Validate photo taking settings
autoPhotoDelay = Mathf.Max(0f, autoPhotoDelay);
}
}
}

View File

@@ -14,6 +14,15 @@ namespace Core.Settings
Hold // New hold-based input
}
/// <summary>
/// Enum defining when photos are taken
/// </summary>
public enum PhotoTakingMode
{
Input, // Player must tap/hold to initiate photo sequence
Automatic // Photos are taken automatically when player enters range
}
/// <summary>
/// Interface for player movement settings (used by all player controllers)
/// </summary>
@@ -149,6 +158,10 @@ namespace Core.Settings
float MaxSizePercent { get; }
public float MinSizePercent { get; }
public PhotoInputModes PhotoInputMode { get; }
// Photo Taking Mode
PhotoTakingMode PhotoTakingMode { get; } // Whether photos are taken via input or automatically
float AutoPhotoDelay { get; } // Delay before automatic photo (when PhotoTakingMode is Automatic)
}
/// <summary>

View File

@@ -1,4 +1,4 @@
using AppleHills.Core.Interfaces;
using AppleHills.Core.Interfaces;
using Cinematics;
using Core;
using Core.Lifecycle;
@@ -44,7 +44,7 @@ namespace Minigames.DivingForPictures
private float _currentSpawnProbability;
private float _lastSpawnTime = -100f;
private float _timeSinceLastSpawn = 0f;
private List<Monster> _activeMonsters = new List<Monster>();
private List<Monster.Monster> _activeMonsters = new List<Monster.Monster>();
// Velocity management
// Velocity state tracking
@@ -59,8 +59,8 @@ namespace Minigames.DivingForPictures
// Events
public event Action<int> OnScoreChanged;
public event Action<Monster> OnMonsterSpawned;
public event Action<Monster, int> OnPictureTaken;
public event Action<Monster.Monster> OnMonsterSpawned;
public event Action<Monster.Monster, int> OnPictureTaken;
public event Action<float> OnSpawnProbabilityChanged;
public event Action OnGameOver;
public event Action<int> OnRopeBroken; // Passes remaining ropes count
@@ -86,15 +86,16 @@ namespace Minigames.DivingForPictures
// Photo sequence state
private bool _isPhotoSequenceActive = false;
private Monster _currentPhotoTarget = null;
private Monster.Monster _currentPhotoTarget = null;
private float _capturedProximity = 0f;
private Coroutine _autoPhotoCoroutine = null; // Coroutine for automatic photo taking
// List of components to exempt from pausing during photo sequence
private List<IPausable> _exemptFromPhotoSequencePausing = new List<IPausable>();
// Photo sequence events
public event Action<Monster> OnPhotoSequenceStarted;
public event Action<Monster, float> OnPhotoSequenceCompleted; // Now includes proximity score
public event Action<Monster.Monster> OnPhotoSequenceStarted;
public event Action<Monster.Monster, float> OnPhotoSequenceCompleted; // Now includes proximity score
private static DivingGameManager _instance = null;
@@ -151,6 +152,7 @@ namespace Minigames.DivingForPictures
viewfinderManager.OnProximityUpdated += OnProximityUpdated;
viewfinderManager.OnViewfinderTappedDuringAnimation += OnViewfinderTappedDuringAnimation;
viewfinderManager.OnReverseAnimationStarted += OnReverseAnimationStarted;
viewfinderManager.OnPerfectPositionReached += OnPerfectPositionReached;
// TODO: Give this a second look and make sure this is correct
// Add the viewfinder manager to exempt list to ensure it keeps working during photo sequences
@@ -183,6 +185,7 @@ namespace Minigames.DivingForPictures
viewfinderManager.OnProximityUpdated -= OnProximityUpdated;
viewfinderManager.OnViewfinderTappedDuringAnimation -= OnViewfinderTappedDuringAnimation;
viewfinderManager.OnReverseAnimationStarted -= OnReverseAnimationStarted;
viewfinderManager.OnPerfectPositionReached -= OnPerfectPositionReached;
}
}
@@ -255,7 +258,7 @@ namespace Minigames.DivingForPictures
// Instantiate monster at spawn point position
GameObject monsterObj = Instantiate(prefab, spawnPoint.position, Quaternion.identity);
Monster monster = monsterObj.GetComponent<Monster>();
Monster.Monster monster = monsterObj.GetComponent<Monster.Monster>();
if (monster != null)
{
@@ -281,7 +284,7 @@ namespace Minigames.DivingForPictures
}
}
private void DoPictureTaken(Monster monster)
private void DoPictureTaken(Monster.Monster monster)
{
// Calculate points based on depth
int depthBonus = Mathf.FloorToInt(Mathf.Abs(monster.transform.position.y) * _settings.DepthMultiplier);
@@ -838,14 +841,57 @@ namespace Minigames.DivingForPictures
component.DoResume();
}
// Change input mode to UI when menu is open
InputManager.Instance.SetInputMode(InputMode.GameAndUI);
// Restore game input mode
InputManager.Instance.SetInputMode(InputMode.Game);
Logging.Debug($"[DivingGameManager] Game resumed. Resumed {_pausableComponents.Count} components.");
}
#region Photo Sequence Methods
/// <summary>
/// Coroutine to automatically take a photo after a configured delay
/// </summary>
private IEnumerator AutomaticPhotoSequence(Monster.Monster monster)
{
// Wait for the configured delay
yield return new WaitForSeconds(_settings.AutoPhotoDelay);
// Check if monster is still valid and player is still in range
if (monster != null && monster.IsPlayerInDetectionRange && !_isPhotoSequenceActive && !monster.IsPictureTaken)
{
// Start photo sequence automatically
_isPhotoSequenceActive = true;
_currentPhotoTarget = monster;
// Pause the game
DoPause(false);
// Mark monster as in photo sequence
monster.SetPhotoSequenceInProgress();
// Reset captured proximity
_capturedProximity = 0f;
// Notify listeners
OnPhotoSequenceStarted?.Invoke(monster);
// Start the viewfinder animation
if (viewfinderManager != null)
{
viewfinderManager.StartViewfinderSequence(monster.transform);
Logging.Debug($"[DivingGameManager] Automatically started photo sequence for monster {monster.name}");
}
else
{
Debug.LogError("[DivingGameManager] No ViewfinderManager available!");
CompletePhotoSequence();
}
}
_autoPhotoCoroutine = null;
}
/// <summary>
/// Called when the viewfinder animation phase changes to reverse (zoom out)
/// </summary>
@@ -854,6 +900,22 @@ namespace Minigames.DivingForPictures
Logging.Debug("[DivingGameManager] Viewfinder animation entering reverse (zoom-out) phase");
}
/// <summary>
/// Called when the viewfinder reaches perfect position (end of zoom-in phase)
/// In automatic mode, this triggers the photo capture
/// </summary>
private void OnPerfectPositionReached()
{
Logging.Debug("[DivingGameManager] Viewfinder reached perfect position");
// In automatic mode, take the photo when perfect position is reached
if (_settings.PhotoTakingMode == PhotoTakingMode.Automatic && _isPhotoSequenceActive)
{
Logging.Debug("[DivingGameManager] Automatic mode - taking photo at perfect position");
TakePicture();
}
}
/// <summary>
/// Called when proximity value is updated during animation
/// </summary>
@@ -888,6 +950,12 @@ namespace Minigames.DivingForPictures
if (!_isPhotoSequenceActive || _currentPhotoTarget == null)
return;
// Stop the viewfinder animation if it's still running
if (viewfinderManager != null)
{
viewfinderManager.StopViewfinderAnimation();
}
// Notify the monster that its picture was taken
_currentPhotoTarget.NotifyPictureTaken();
@@ -914,7 +982,7 @@ namespace Minigames.DivingForPictures
/// <summary>
/// Calculates the score for a picture based on proximity to target and monster depth
/// </summary>
private void CalculateScore(Monster monster, float proximity)
private void CalculateScore(Monster.Monster monster, float proximity)
{
if (monster == null) return;
@@ -1035,7 +1103,7 @@ namespace Minigames.DivingForPictures
#endregion
private void DoMonsterSpawned(Monster monster)
private void DoMonsterSpawned(Monster.Monster monster)
{
if (monster != null)
{
@@ -1049,7 +1117,7 @@ namespace Minigames.DivingForPictures
}
}
private void DoMonsterDespawned(Monster monster)
private void DoMonsterDespawned(Monster.Monster monster)
{
// Remove from active list
_activeMonsters.Remove(monster);
@@ -1064,29 +1132,47 @@ namespace Minigames.DivingForPictures
}
// Handles player entering monster detection range
private void OnPlayerEnterMonsterRange(Monster monster)
private void OnPlayerEnterMonsterRange(Monster.Monster monster)
{
if (monster != null && !_isPhotoSequenceActive)
{
// Store current target for later use
_currentPhotoTarget = monster;
// Show the full-screen viewfinder (first step in two-step process)
if (viewfinderManager != null)
// Check the photo taking mode from settings
if (_settings.PhotoTakingMode == PhotoTakingMode.Automatic)
{
viewfinderManager.ShowFullScreenViewfinder();
Logging.Debug($"[DivingGameManager] Player entered range of monster {monster.name}, showing full-screen viewfinder");
// Automatic mode: Start coroutine to automatically take photo after delay
_autoPhotoCoroutine = StartCoroutine(AutomaticPhotoSequence(monster));
Logging.Debug($"[DivingGameManager] Player entered range of monster {monster.name}, starting automatic photo sequence");
}
else
{
// Input mode: Show the full-screen viewfinder (first step in two-step process)
if (viewfinderManager != null)
{
viewfinderManager.ShowFullScreenViewfinder();
Logging.Debug($"[DivingGameManager] Player entered range of monster {monster.name}, showing full-screen viewfinder");
}
}
}
}
// Handles player exiting monster detection range
private void OnPlayerExitMonsterRange(Monster monster)
private void OnPlayerExitMonsterRange(Monster.Monster monster)
{
// Only hide viewfinder if we're not already in a photo sequence
if (!_isPhotoSequenceActive && monster == _currentPhotoTarget)
{
// Hide the viewfinder
// Cancel automatic photo coroutine if it's running
if (_autoPhotoCoroutine != null)
{
StopCoroutine(_autoPhotoCoroutine);
_autoPhotoCoroutine = null;
Logging.Debug($"[DivingGameManager] Player exited range, cancelled automatic photo sequence");
}
// Hide the viewfinder (only relevant for input mode)
if (viewfinderManager != null)
{
viewfinderManager.HideViewfinder();

View File

@@ -44,7 +44,7 @@ namespace Minigames.DivingForPictures
}
}
private void ShowScorePopup(Monster monster, int points)
private void ShowScorePopup(Monster.Monster monster, int points)
{
if (scorePopupPrefab == null) return;

View File

@@ -1,23 +1,24 @@
using AudioSourceEvents;
using Core;
using System;
using System;
using System.Collections;
using AudioSourceEvents;
using Core;
using UnityEngine;
using UnityEngine.Audio;
namespace Minigames.DivingForPictures
namespace Minigames.DivingForPictures.Monster
{
public class Monster : MonoBehaviour
{
[Header("References")]
[SerializeField] private CircleCollider2D detectionCollider;
[SerializeField] private GameObject proximityIndicator; // Visual sprite showing detection range
private bool pictureAlreadyTaken = false;
private bool photoSequenceInProgress = false;
private UnityEngine.Camera mainCamera;
private bool _pictureAlreadyTaken = false;
private bool _photoSequenceInProgress = false;
private Camera _mainCamera;
// Track if player is in detection range
private bool playerInDetectionRange = false;
private bool _playerInDetectionRange = false;
// Events
public event Action<Monster> OnMonsterDespawned;
@@ -26,18 +27,18 @@ namespace Minigames.DivingForPictures
// Properties
public float PictureRadius => detectionCollider != null ? detectionCollider.radius : 0f;
public bool IsPhotoSequenceInProgress => photoSequenceInProgress;
public bool IsPlayerInDetectionRange => playerInDetectionRange;
public bool IsPictureTaken => pictureAlreadyTaken;
public bool IsPhotoSequenceInProgress => _photoSequenceInProgress;
public bool IsPlayerInDetectionRange => _playerInDetectionRange;
public bool IsPictureTaken => _pictureAlreadyTaken;
public enum MonsterSounds
{Hello, RareBeast, Pooping,Smile,Whatsup1, Whatsup2}
private AudioSource _audioSource;
public AudioResource[] monsterClips;
private AudioSource playerAudioSource;
private AudioSource _playerAudioSource;
public AudioResource monsterSpottedAudio;
private IAudioEventSource _eventSource;
private GameObject playerObject;
private GameObject _playerObject;
private void Awake()
{
@@ -46,37 +47,37 @@ namespace Minigames.DivingForPictures
if (detectionCollider == null)
detectionCollider = GetComponent<CircleCollider2D>();
mainCamera = UnityEngine.Camera.main;
_mainCamera = UnityEngine.Camera.main;
_audioSource = GetComponent<AudioSource>();
playerObject = GameObject.FindGameObjectsWithTag("Player")[0];
playerAudioSource = playerObject.GetComponent<AudioSource>();
_eventSource = playerAudioSource.RequestEventHandlers();
_eventSource.AudioStopped += playerAudioDone;
_playerObject = GameObject.FindGameObjectsWithTag("Player")[0];
_playerAudioSource = _playerObject.GetComponent<AudioSource>();
_eventSource = _playerAudioSource.RequestEventHandlers();
_eventSource.AudioStopped += PlayerAudioDone;
// Start checking if monster is off-screen
StartCoroutine(CheckIfOffScreen());
}
private void playerAudioDone(object sender, EventArgs e)
private void PlayerAudioDone(object sender, EventArgs e)
{
_audioSource.Play();
}
private void OnEnable()
{
playerAudioSource.resource = monsterSpottedAudio;
playerAudioSource.Play();
pictureAlreadyTaken = false;
photoSequenceInProgress = false;
_playerAudioSource.resource = monsterSpottedAudio;
_playerAudioSource.Play();
_pictureAlreadyTaken = false;
_photoSequenceInProgress = false;
}
private void OnDestroy()
{
Logging.Debug("Monster destroyed: " + gameObject.name);
_eventSource.AudioStopped -= playerAudioDone;
_eventSource.AudioStopped -= PlayerAudioDone;
}
private IEnumerator CheckIfOffScreen()
@@ -97,15 +98,15 @@ namespace Minigames.DivingForPictures
private bool IsVisibleToCamera()
{
if (mainCamera == null)
mainCamera = UnityEngine.Camera.main;
if (_mainCamera == null)
_mainCamera = UnityEngine.Camera.main;
if (mainCamera == null)
if (_mainCamera == null)
return false;
// Get the world position (will account for parent movement)
Vector3 worldPosition = transform.position;
Vector3 viewportPoint = mainCamera.WorldToViewportPoint(worldPosition);
Vector3 viewportPoint = _mainCamera.WorldToViewportPoint(worldPosition);
// If z is negative, the object is behind the camera
if (viewportPoint.z < 0)
@@ -129,10 +130,14 @@ namespace Minigames.DivingForPictures
private void OnTriggerEnter2D(Collider2D other)
{
// Don't trigger if picture already taken - prevents re-triggering photo sequence
if (_pictureAlreadyTaken)
return;
// Check if it's the player
if (other.CompareTag("Player") && !pictureAlreadyTaken)
if (other.CompareTag("Player"))
{
playerInDetectionRange = true;
_playerInDetectionRange = true;
// Fire the event so the game manager can display the viewfinder without pausing
OnPlayerEnterDetectionRange?.Invoke(this);
}
@@ -140,10 +145,14 @@ namespace Minigames.DivingForPictures
private void OnTriggerExit2D(Collider2D other)
{
// Don't trigger if picture already taken
if (_pictureAlreadyTaken)
return;
// Check if it's the player
if (other.CompareTag("Player"))
{
playerInDetectionRange = false;
_playerInDetectionRange = false;
// Fire the event so the game manager can hide the viewfinder
OnPlayerExitDetectionRange?.Invoke(this);
}
@@ -154,8 +163,8 @@ namespace Minigames.DivingForPictures
/// </summary>
public void SetPhotoSequenceInProgress(bool inProgress = true)
{
if (pictureAlreadyTaken) return;
photoSequenceInProgress = inProgress;
if (_pictureAlreadyTaken) return;
_photoSequenceInProgress = inProgress;
}
/// <summary>
@@ -164,10 +173,19 @@ namespace Minigames.DivingForPictures
/// </summary>
public void NotifyPictureTaken()
{
if (pictureAlreadyTaken) return;
if (_pictureAlreadyTaken) return;
pictureAlreadyTaken = true;
photoSequenceInProgress = false;
_pictureAlreadyTaken = true;
_photoSequenceInProgress = false;
// Destroy the proximity indicator visual
if (proximityIndicator != null)
{
Destroy(proximityIndicator);
proximityIndicator = null;
}
Logging.Debug($"[Monster] {gameObject.name} picture taken, collider still enabled for debugging");
}
/// <summary>

View File

@@ -1,7 +1,6 @@
using UnityEngine;
using System;
using System.Collections;
using AppleHills.Core.Settings;
using Core;
using Core.Settings;
using UnityEngine.Audio;
@@ -71,6 +70,7 @@ namespace Minigames.DivingForPictures.PictureCamera
public event Action OnAnimationStarted;
public event Action OnAnimationCompleted;
public event Action OnReverseAnimationStarted; // New event for when zoom-out phase starts
public event Action OnPerfectPositionReached; // New event for when viewfinder reaches perfect position (end of zoom-in)
public event Action<float> OnProximityUpdated; // New event for proximity updates
public event Action<float> OnProgressThresholdReached; // Fires at configured thresholds (25%, 50%, etc.)
public event Action OnViewfinderTapped; // Event when viewfinder is tapped during normal display
@@ -308,8 +308,16 @@ namespace Minigames.DivingForPictures.PictureCamera
if (viewfinderInstance != null)
{
// Remove input override before destroying
if (viewfinderComponent != null)
{
viewfinderComponent.RemoveInputOverride();
}
Destroy(viewfinderInstance);
viewfinderInstance = null;
viewfinderComponent = null;
viewfinderRectTransform = null;
}
isAnimating = false;
@@ -484,8 +492,17 @@ namespace Minigames.DivingForPictures.PictureCamera
{
if (viewfinderInstance != null)
{
// Remove input override before destroying old viewfinder
if (viewfinderComponent != null)
{
viewfinderComponent.RemoveInputOverride();
}
// Already showing a viewfinder, destroy it first
Destroy(viewfinderInstance);
viewfinderInstance = null;
viewfinderComponent = null;
viewfinderRectTransform = null;
}
if (settings.ViewfinderPrefab == null)
@@ -712,6 +729,9 @@ namespace Minigames.DivingForPictures.PictureCamera
currentProximity = 1.0f; // At target
OnProximityUpdated?.Invoke(currentProximity);
// Fire event that we've reached perfect position
OnPerfectPositionReached?.Invoke();
// Brief pause at target
yield return new WaitForSecondsRealtime(0.2f);