Merge branch 'main' into DamianBranch

This commit is contained in:
2025-10-14 11:29:29 +00:00
26 changed files with 1742 additions and 254 deletions

View File

@@ -342,17 +342,17 @@ LineRenderer:
m_SortingLayer: 0
m_SortingOrder: 0
m_Positions:
- {x: -0.15602553, y: 3.8573277, z: 0}
- {x: -0.1566351, y: 3.753693, z: 0}
- {x: -0.1572447, y: 3.6518767, z: 0}
- {x: -0.15785426, y: 3.5518785, z: 0}
- {x: -0.15846384, y: 3.453699, z: 0}
- {x: -0.15907341, y: 3.3573375, z: 0}
- {x: -0.15968299, y: 3.2627945, z: 0}
- {x: -0.16029257, y: 3.1700697, z: 0}
- {x: -0.16090216, y: 3.0791638, z: 0}
- {x: -0.16151173, y: 2.9900756, z: 0}
- {x: -0.16212131, y: 2.9028063, z: 0}
- {x: -0.15602553, y: 4.0749445, z: 0}
- {x: -0.1566351, y: 3.9736378, z: 0}
- {x: -0.1572447, y: 3.8729858, z: 0}
- {x: -0.15785426, y: 3.7729874, z: 0}
- {x: -0.15846384, y: 3.6736438, z: 0}
- {x: -0.15907341, y: 3.5749543, z: 0}
- {x: -0.15968299, y: 3.4769194, z: 0}
- {x: -0.16029257, y: 3.3795385, z: 0}
- {x: -0.16090216, y: 3.2828126, z: 0}
- {x: -0.16151173, y: 3.1867406, z: 0}
- {x: -0.16212131, y: 3.0913231, z: 0}
m_Parameters:
serializedVersion: 3
widthMultiplier: 1
@@ -454,6 +454,7 @@ GameObject:
- component: {fileID: 224729333}
- component: {fileID: 224729332}
- component: {fileID: 224729334}
- component: {fileID: 224729335}
m_Layer: 0
m_Name: CinemachineCamera
m_TagString: Untagged
@@ -541,6 +542,21 @@ Animator:
m_AllowConstantClipSamplingOptimization: 1
m_KeepAnimatorStateOnDisable: 0
m_WriteDefaultValuesOnDisable: 0
--- !u!114 &224729335
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 224729330}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 8a71a21143bd4f4992d08829084d1e3b, type: 3}
m_Name:
m_EditorClassIdentifier: AppleHillsScripts::AppleHillsCamera.CameraScreenAdapter
referenceMarker: {fileID: 1651034645}
adjustOnStart: 1
adjustOnScreenResize: 1
--- !u!1 &323864663
GameObject:
m_ObjectHideFlags: 0
@@ -882,6 +898,7 @@ GameObject:
- component: {fileID: 747976404}
- component: {fileID: 747976405}
- component: {fileID: 747976406}
- component: {fileID: 747976407}
m_Layer: 0
m_Name: BottleMarine
m_TagString: Player
@@ -898,7 +915,7 @@ Transform:
m_GameObject: {fileID: 747976396}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 2.9799, z: 0}
m_LocalPosition: {x: 0, y: 3.1975174, z: 0}
m_LocalScale: {x: 0.57574, y: 0.57574, z: 0.57574}
m_ConstrainProportionsScale: 0
m_Children:
@@ -918,6 +935,7 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: d39dbaae819c4a128a11ca60fbbc98c9, type: 3}
m_Name:
m_EditorClassIdentifier:
edgeAnchor: {fileID: 747976407}
--- !u!114 &747976399
MonoBehaviour:
m_ObjectHideFlags: 0
@@ -1089,6 +1107,31 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier: AppleHillsScripts::Minigames.DivingForPictures.Utilities.BottlePauser
wobbleReference: {fileID: 747976399}
--- !u!114 &747976407
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 747976396}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: ed380d10e1e04ae7990e5c726c929063, type: 3}
m_Name:
m_EditorClassIdentifier: AppleHillsScripts::AppleHillsCamera.EdgeAnchor
referenceMarker: {fileID: 1651034645}
cameraAdapter: {fileID: 224729335}
anchorEdge: 0
useReferenceMargin: 0
customMargin: 2
adjustOnStart: 1
adjustOnScreenResize: 0
preserveOtherAxes: 1
accountForObjectSize: 1
showVisualization: 1
visualizationColor: {r: 1, g: 0, b: 1, a: 1}
showObjectBounds: 1
debugMode: 0
--- !u!1 &824396214
GameObject:
m_ObjectHideFlags: 0
@@ -1324,17 +1367,17 @@ LineRenderer:
m_SortingLayer: 0
m_SortingOrder: 0
m_Positions:
- {x: -0.15602553, y: 3.8573277, z: 0}
- {x: -0.11662118, y: 3.66006, z: 0}
- {x: -0.07721684, y: 3.4853156, z: 0}
- {x: -0.03781248, y: 3.3330944, z: 0}
- {x: 0.0015918687, y: 3.2033968, z: 0}
- {x: 0.040996216, y: 3.0962222, z: 0}
- {x: 0.08040057, y: 3.011571, z: 0}
- {x: 0.11980491, y: 2.9494433, z: 0}
- {x: 0.15920927, y: 2.9098392, z: 0}
- {x: 0.1986136, y: 2.892758, z: 0}
- {x: 0.23801796, y: 2.8982003, z: 0}
- {x: -0.15602553, y: 4.074945, z: 0}
- {x: -0.11662118, y: 3.8796225, z: 0}
- {x: -0.07721684, y: 3.7057445, z: 0}
- {x: -0.03781248, y: 3.5533106, z: 0}
- {x: 0.0015918687, y: 3.4223216, z: 0}
- {x: 0.040996216, y: 3.3127766, z: 0}
- {x: 0.08040057, y: 3.2246757, z: 0}
- {x: 0.11980491, y: 3.1580195, z: 0}
- {x: 0.15920927, y: 3.1128078, z: 0}
- {x: 0.1986136, y: 3.0890403, z: 0}
- {x: 0.23801796, y: 3.0867171, z: 0}
m_Parameters:
serializedVersion: 3
widthMultiplier: 1
@@ -1867,17 +1910,17 @@ LineRenderer:
m_SortingLayer: 0
m_SortingOrder: 0
m_Positions:
- {x: -0.15602553, y: 3.8573277, z: 0}
- {x: -0.18956745, y: 3.6568341, z: 0}
- {x: -0.22310936, y: 3.479415, z: 0}
- {x: -0.25665125, y: 3.3250687, z: 0}
- {x: -0.29019317, y: 3.193797, z: 0}
- {x: -0.32373506, y: 3.0855987, z: 0}
- {x: -0.35727698, y: 3.0004745, z: 0}
- {x: -0.39081886, y: 2.938424, z: 0}
- {x: -0.4243608, y: 2.8994474, z: 0}
- {x: -0.45790267, y: 2.8835444, z: 0}
- {x: -0.4914446, y: 2.8907156, z: 0}
- {x: -0.15602553, y: 4.074945, z: 0}
- {x: -0.18956745, y: 3.8764977, z: 0}
- {x: -0.22310936, y: 3.7000232, z: 0}
- {x: -0.25665125, y: 3.5455205, z: 0}
- {x: -0.29019317, y: 3.4129908, z: 0}
- {x: -0.32373506, y: 3.3024333, z: 0}
- {x: -0.35727698, y: 3.213848, z: 0}
- {x: -0.39081886, y: 3.1472356, z: 0}
- {x: -0.4243608, y: 3.1025953, z: 0}
- {x: -0.45790267, y: 3.0799277, z: 0}
- {x: -0.4914446, y: 3.0792325, z: 0}
m_Parameters:
serializedVersion: 3
widthMultiplier: 1
@@ -1968,6 +2011,59 @@ MonoBehaviour:
ropeDamping: 0.3
initialSeparationDistance: 0.1
initialFallImpulse: 2
--- !u!1 &1651034644
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1651034646}
- component: {fileID: 1651034645}
m_Layer: 0
m_Name: ScreenReferenceMarker
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!114 &1651034645
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1651034644}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 3058fe4801134fea916ad685f924668f, type: 3}
m_Name:
m_EditorClassIdentifier: AppleHillsScripts::AppleHillsCamera.ScreenReferenceMarker
targetWidth: 6.2
topMargin: 0.2
bottomMargin: 0.2
leftMargin: 0.2
rightMargin: 0.2
gizmoColor: {r: 0, g: 1, b: 0, a: 0.5}
screenEdgeColor: {r: 1, g: 1, b: 0, a: 0.3}
showVerticalMargins: 1
showHorizontalMargins: 1
--- !u!4 &1651034646
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1651034644}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &1679185997
GameObject:
m_ObjectHideFlags: 0
@@ -2031,6 +2127,7 @@ MonoBehaviour:
- {fileID: 2956826569642009690, guid: b3501bc08c6afbf4f868bc7b6c2a2d92, type: 3}
- {fileID: 2956826569642009690, guid: 4f9c8bd3fb844484492a54a2998b9b3b, type: 3}
- {fileID: 2956826569642009690, guid: 16ad8a46d2c7c534982f4ee943e06f74, type: 3}
initialTilePrefab: {fileID: 0}
onTileSpawned:
m_PersistentCalls:
m_Calls: []
@@ -2040,6 +2137,22 @@ MonoBehaviour:
onLastTileLeft:
m_PersistentCalls:
m_Calls: []
depthDifficultyRanges:
- minDepth: 0
maxDepth: 10
difficulty: 1
- minDepth: 11
maxDepth: 20
difficulty: 2
- minDepth: 21
maxDepth: 30
difficulty: 3
- minDepth: 31
maxDepth: 40
difficulty: 4
- minDepth: 41
maxDepth: 2147483647
difficulty: 5
--- !u!1 &1787733334
GameObject:
m_ObjectHideFlags: 0
@@ -2337,7 +2450,7 @@ Transform:
m_GameObject: {fileID: 2106431001}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: -0.165, y: 2.509, z: 0}
m_LocalPosition: {x: -0.165, y: 2.697517, z: 0}
m_LocalScale: {x: 0.57574, y: 0.57574, z: 0.57574}
m_ConstrainProportionsScale: 0
m_Children:
@@ -2366,7 +2479,7 @@ MonoBehaviour:
bottleWobble: {fileID: 747976399}
followStiffness: 4
useWobbleOffset: 1
baseY: 2.5
verticalDistance: 0.5
--- !u!114 &2106431004
MonoBehaviour:
m_ObjectHideFlags: 0
@@ -2410,3 +2523,4 @@ SceneRoots:
- {fileID: 1679185998}
- {fileID: 323864665}
- {fileID: 424805726}
- {fileID: 1651034646}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 95f834c9cbc34cff9490c5582d66e463
timeCreated: 1760359480

View File

@@ -0,0 +1,127 @@
using UnityEngine;
using Unity.Cinemachine;
using System;
namespace AppleHillsCamera
{
/// <summary>
/// Adjusts the camera's orthographic size to match the target width from a ScreenReferenceMarker.
/// Works with both regular cameras and Cinemachine virtual cameras.
/// </summary>
public class CameraScreenAdapter : MonoBehaviour
{
[Tooltip("Reference that defines the target width to match")]
public ScreenReferenceMarker referenceMarker;
[Tooltip("Whether to adjust the camera automatically on Start")]
public bool adjustOnStart = true;
[Tooltip("Whether to adjust the camera automatically when the screen size changes")]
public bool adjustOnScreenResize = true;
// Event that other components can subscribe to when camera is adjusted
public event Action OnCameraAdjusted;
private Camera _regularCamera;
private CinemachineCamera _virtualCamera;
private int _lastScreenWidth;
private int _lastScreenHeight;
private bool _usingCinemachine;
private void Awake()
{
// Try to get regular camera first
_regularCamera = GetComponent<Camera>();
// Try to get Cinemachine camera if no regular camera or if both exist
_virtualCamera = GetComponent<CinemachineCamera>();
// Determine which camera type we're using
_usingCinemachine = _virtualCamera != null;
_lastScreenWidth = Screen.width;
_lastScreenHeight = Screen.height;
if (!_usingCinemachine && _regularCamera == null)
{
Debug.LogError("CameraScreenAdapter: No camera component found. Add this script to a GameObject with either Camera or CinemachineCamera component.");
enabled = false;
return;
}
}
private void Start()
{
if (adjustOnStart)
{
AdjustCamera();
}
}
private void Update()
{
if (adjustOnScreenResize &&
(Screen.width != _lastScreenWidth || Screen.height != _lastScreenHeight))
{
AdjustCamera();
_lastScreenWidth = Screen.width;
_lastScreenHeight = Screen.height;
}
}
/// <summary>
/// Manually trigger camera adjustment to match the reference width.
/// </summary>
public void AdjustCamera()
{
if (referenceMarker == null)
{
Debug.LogWarning("CameraScreenAdapter: Missing reference marker.");
return;
}
// Calculate the orthographic size based on the target width and screen aspect ratio
float targetWidth = referenceMarker.targetWidth;
float screenAspect = (float)Screen.height / Screen.width;
// Orthographic size is half the height, so we calculate:
// orthoSize = (targetWidth / 2) * (screenHeight / screenWidth)
float orthoSize = (targetWidth / 2f) * screenAspect;
// Apply the calculated size to the camera
if (_usingCinemachine)
{
// Apply to Cinemachine virtual camera
var lens = _virtualCamera.Lens;
lens.OrthographicSize = orthoSize;
_virtualCamera.Lens = lens;
Debug.Log($"Cinemachine Camera adapted: Width={targetWidth}, Aspect={screenAspect:F2}, OrthoSize={orthoSize:F2}");
}
else
{
// Apply to regular camera
if (_regularCamera.orthographic)
{
_regularCamera.orthographicSize = orthoSize;
Debug.Log($"Camera adapted: Width={targetWidth}, Aspect={screenAspect:F2}, OrthoSize={orthoSize:F2}");
}
else
{
Debug.LogWarning("CameraScreenAdapter: Regular camera is not in orthographic mode.");
return;
}
}
// Notify subscribers that the camera has been adjusted
OnCameraAdjusted?.Invoke();
}
/// <summary>
/// Gets the camera component being controlled by this adapter
/// </summary>
public Camera GetControlledCamera()
{
return _usingCinemachine ? _virtualCamera.GetComponent<Camera>() : _regularCamera;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 8a71a21143bd4f4992d08829084d1e3b
timeCreated: 1760359498

View File

@@ -0,0 +1,549 @@
using AppleHills.Core;
using UnityEngine;
using System;
namespace AppleHillsCamera
{
/// <summary>
/// Anchors a game object at a fixed distance from a screen edge.
/// </summary>
[ExecuteInEditMode] // Make it run in the editor
public class EdgeAnchor : MonoBehaviour
{
// Event that fires when the anchor's position is updated
public event Action OnPositionUpdated;
public enum AnchorEdge
{
Top,
Bottom,
Left,
Right
}
[Tooltip("Reference marker that defines the screen edges and margins")]
public ScreenReferenceMarker referenceMarker;
[Tooltip("Camera adapter to subscribe to for runtime updates")]
public CameraScreenAdapter cameraAdapter;
[Tooltip("Which screen edge to anchor to")]
public AnchorEdge anchorEdge = AnchorEdge.Top;
[Tooltip("Whether to use the predefined margin from the reference marker")]
public bool useReferenceMargin = true;
[Tooltip("Custom margin to use if not using the reference margin")]
public float customMargin = 1f;
[Tooltip("Whether to adjust the position automatically on Start")]
public bool adjustOnStart = true;
[Tooltip("Whether to adjust the position when the screen size changes")]
public bool adjustOnScreenResize = true;
[Tooltip("Whether to preserve the object's position on other axes")]
public bool preserveOtherAxes = true;
[Tooltip("Whether to account for this object's size in positioning")]
public bool accountForObjectSize = true;
[Header("Visualization")]
[Tooltip("Whether to show the anchor visualization in the editor")]
public bool showVisualization = true;
[Tooltip("Color for the anchor visualization line")]
public Color visualizationColor = new Color(1f, 0f, 0f, 0.8f);
[Tooltip("Show object bounds in visualization")]
public bool showObjectBounds = true;
[Tooltip("Debug mode - print position changes to console")]
public bool debugMode = false;
private Camera _camera;
private int _lastScreenWidth;
private int _lastScreenHeight;
private float _lastOrthoSize = 0f;
private Vector3 _originalPosition;
private bool _initialized = false;
private Bounds _objectBounds;
#if UNITY_EDITOR
private void OnDrawGizmos()
{
if (!showVisualization || referenceMarker == null)
return;
// Find camera if needed
if (_camera == null)
FindCamera();
if (_camera == null)
return;
// Calculate the anchor point (the exact point on the screen edge)
Vector3 anchorPoint = CalculateScreenEdgePoint();
// Save original color
Color originalColor = Gizmos.color;
// Draw line to anchor point
Gizmos.color = visualizationColor;
Gizmos.DrawLine(transform.position, anchorPoint);
// Draw a small sphere at the anchor point
Gizmos.DrawSphere(anchorPoint, 0.1f);
// Draw object bounds if enabled
if (showObjectBounds && accountForObjectSize)
{
Bounds bounds = GetObjectBounds();
if (bounds.size != Vector3.zero)
{
Color boundsColor = visualizationColor;
boundsColor.a *= 0.3f;
Gizmos.color = boundsColor;
Gizmos.DrawWireCube(bounds.center, bounds.size);
}
}
// Restore original color
Gizmos.color = originalColor;
}
#endif
private void Awake()
{
_originalPosition = transform.position;
FindCamera();
if (_camera != null)
{
_lastOrthoSize = _camera.orthographicSize;
}
_lastScreenWidth = Screen.width;
_lastScreenHeight = Screen.height;
_initialized = true;
}
private void OnEnable()
{
if (!_initialized)
{
_originalPosition = transform.position;
FindCamera();
_lastScreenWidth = Screen.width;
_lastScreenHeight = Screen.height;
_initialized = true;
}
// Subscribe to camera adapter events
if (Application.isPlaying && cameraAdapter != null)
{
cameraAdapter.OnCameraAdjusted += HandleCameraAdjusted;
}
// Adjust position immediately when enabled in editor
#if UNITY_EDITOR
if (!Application.isPlaying)
{
UpdatePosition();
}
#endif
}
private void OnDisable()
{
// Unsubscribe from camera adapter events
if (cameraAdapter != null)
{
cameraAdapter.OnCameraAdjusted -= HandleCameraAdjusted;
}
}
private void HandleCameraAdjusted()
{
// Update position when camera is adjusted
if (Application.isPlaying)
{
if (debugMode)
{
Debug.Log($"Camera adjusted event received by {gameObject.name}, updating position");
}
// Ensure we have the latest camera reference
FindCamera();
UpdatePosition();
}
}
private void Start()
{
// If no camera adapter was manually set, try to find one in the scene
if (cameraAdapter == null && Application.isPlaying)
{
FindCameraAdapter();
}
// Ensure we're subscribed to camera adapter events
if (cameraAdapter != null && Application.isPlaying)
{
cameraAdapter.OnCameraAdjusted -= HandleCameraAdjusted; // Remove any duplicate subscriptions
cameraAdapter.OnCameraAdjusted += HandleCameraAdjusted; // Subscribe
}
if (adjustOnStart && Application.isPlaying)
{
UpdatePosition();
}
}
private void FindCameraAdapter()
{
// Try to find the camera adapter in the scene
var adapters = FindObjectsOfType<CameraScreenAdapter>();
if (adapters != null && adapters.Length > 0)
{
// Prioritize any adapter that's on the same camera we're using
foreach (var adapter in adapters)
{
if (_camera != null && adapter.GetControlledCamera() == _camera)
{
cameraAdapter = adapter;
Debug.Log($"EdgeAnchor on {gameObject.name} auto-connected to CameraScreenAdapter on {cameraAdapter.gameObject.name}");
return;
}
}
// If no matching camera found, use the first one
cameraAdapter = adapters[0];
Debug.Log($"EdgeAnchor on {gameObject.name} auto-connected to CameraScreenAdapter on {cameraAdapter.gameObject.name}");
}
}
private void Update()
{
bool shouldUpdate = false;
// Check if we have a valid camera
if (_camera == null)
{
FindCamera();
if (_camera != null) shouldUpdate = true;
}
// Check if camera's ortho size has changed
if (_camera != null && _camera.orthographicSize != _lastOrthoSize)
{
if (debugMode)
{
Debug.Log($"{gameObject.name}: Camera ortho size changed from {_lastOrthoSize} to {_camera.orthographicSize}");
}
_lastOrthoSize = _camera.orthographicSize;
shouldUpdate = true;
}
// Update if screen size has changed
if (adjustOnScreenResize &&
(Screen.width != _lastScreenWidth || Screen.height != _lastScreenHeight))
{
shouldUpdate = true;
_lastScreenWidth = Screen.width;
_lastScreenHeight = Screen.height;
}
// In editor, check for reference marker changes or inspector changes
#if UNITY_EDITOR
if (!Application.isPlaying)
{
shouldUpdate = true;
}
#endif
// Update position if needed
if (shouldUpdate)
{
UpdatePosition();
}
}
private void FindCamera()
{
Camera prevCamera = _camera;
// First check if we have a camera adapter reference
if (cameraAdapter != null)
{
_camera = cameraAdapter.GetControlledCamera();
if (_camera != null)
{
return;
}
}
// Look for the main camera
_camera = Camera.main;
// If no main camera found, try to find any camera
if (_camera == null)
{
_camera = FindAnyObjectByType<Camera>();
}
if (_camera == null)
{
Debug.LogError("EdgeAnchor: No camera found in the scene.");
}
else if (_camera != prevCamera && debugMode)
{
Debug.Log($"{gameObject.name}: Camera reference updated to {_camera.name}");
}
}
/// <summary>
/// Get the combined bounds of all renderers on this object and its children
/// </summary>
private Bounds GetObjectBounds()
{
Bounds bounds = new Bounds(transform.position, Vector3.zero);
// Get all renderers in this object and its children
Renderer[] renderers = GetComponentsInChildren<Renderer>();
if (renderers.Length > 0)
{
// Start with the first renderer's bounds
bounds = renderers[0].bounds;
// Expand to include all other renderers
for (int i = 1; i < renderers.Length; i++)
{
bounds.Encapsulate(renderers[i].bounds);
}
}
else
{
// No renderers found, create a small placeholder bounds
bounds = new Bounds(transform.position, new Vector3(0.1f, 0.1f, 0.1f));
}
// Cache the bounds
_objectBounds = bounds;
return bounds;
}
/// <summary>
/// Manually trigger position adjustment based on the anchor settings.
/// </summary>
public void UpdatePosition()
{
if (referenceMarker == null)
{
Debug.LogWarning("EdgeAnchor: Missing reference marker.");
return;
}
if (_camera == null)
{
FindCamera();
if (_camera == null) return;
}
// Get the margin value to use
float margin = GetMarginValue();
// Calculate the new position based on anchor edge and object size
Vector3 newPosition = CalculateAnchoredPosition(margin);
// If preserving other axes, keep their original values
if (preserveOtherAxes)
{
switch (anchorEdge)
{
case AnchorEdge.Top:
case AnchorEdge.Bottom:
newPosition.x = transform.position.x;
newPosition.z = transform.position.z;
break;
case AnchorEdge.Left:
case AnchorEdge.Right:
newPosition.y = transform.position.y;
newPosition.z = transform.position.z;
break;
}
}
// Apply the new position
if (debugMode && Vector3.Distance(transform.position, newPosition) > 0.01f)
{
Debug.Log($"{gameObject.name} position updated: {transform.position} -> {newPosition}, Camera OrthoSize: {_camera.orthographicSize}");
}
transform.position = newPosition;
// Notify listeners that the position has been updated
OnPositionUpdated?.Invoke();
// Store the current ortho size for change detection
if (_camera != null)
{
_lastOrthoSize = _camera.orthographicSize;
}
}
private float GetMarginValue()
{
if (!useReferenceMargin)
{
return customMargin;
}
switch (anchorEdge)
{
case AnchorEdge.Top:
return referenceMarker.topMargin;
case AnchorEdge.Bottom:
return referenceMarker.bottomMargin;
case AnchorEdge.Left:
return referenceMarker.leftMargin;
case AnchorEdge.Right:
return referenceMarker.rightMargin;
default:
return customMargin;
}
}
private Vector3 CalculateAnchoredPosition(float margin)
{
if (_camera == null)
return transform.position;
// Always get the CURRENT camera properties to ensure we have latest values
float cameraOrthoSize = _camera.orthographicSize;
float screenAspect = (float)Screen.width / Screen.height;
float screenHeight = cameraOrthoSize * 2f;
float screenWidth = screenHeight * screenAspect;
Vector3 cameraPosition = _camera.transform.position;
Vector3 newPosition = transform.position;
// Calculate object size offset if needed
float offsetX = 0f;
float offsetY = 0f;
if (accountForObjectSize)
{
Bounds bounds = GetObjectBounds();
Vector3 extents = bounds.extents; // Half the size
Vector3 centerOffset = bounds.center - transform.position; // Offset from pivot to center
switch (anchorEdge)
{
case AnchorEdge.Top:
// For top edge, offset is negative (moving down) by the top extent
offsetY = -extents.y - centerOffset.y;
break;
case AnchorEdge.Bottom:
// For bottom edge, offset is positive (moving up) by the bottom extent
offsetY = extents.y - centerOffset.y;
break;
case AnchorEdge.Left:
// For left edge, offset is positive (moving right) by the left extent
offsetX = extents.x - centerOffset.x;
break;
case AnchorEdge.Right:
// For right edge, offset is negative (moving left) by the right extent
offsetX = -extents.x - centerOffset.x;
break;
}
}
switch (anchorEdge)
{
case AnchorEdge.Top:
// Position from the top of the screen
// When margin is 0, object's top edge is exactly at the top screen edge
newPosition.y = cameraPosition.y + cameraOrthoSize - margin + offsetY;
break;
case AnchorEdge.Bottom:
// Position from the bottom of the screen
// When margin is 0, object's bottom edge is exactly at the bottom screen edge
newPosition.y = cameraPosition.y - cameraOrthoSize + margin + offsetY;
break;
case AnchorEdge.Left:
// Position from the left of the screen
// When margin is 0, object's left edge is exactly at the left screen edge
newPosition.x = cameraPosition.x - (screenWidth / 2f) + margin + offsetX;
break;
case AnchorEdge.Right:
// Position from the right of the screen
// When margin is 0, object's right edge is exactly at the right screen edge
newPosition.x = cameraPosition.x + (screenWidth / 2f) - margin + offsetX;
break;
}
return newPosition;
}
/// <summary>
/// Calculates the exact point on the screen edge for visualization purposes
/// </summary>
private Vector3 CalculateScreenEdgePoint()
{
if (_camera == null)
return transform.position;
// Get the screen edges in world coordinates
float cameraOrthoSize = _camera.orthographicSize;
float screenAspect = (float)Screen.width / Screen.height;
float screenHeight = cameraOrthoSize * 2f;
float screenWidth = screenHeight * screenAspect;
Vector3 cameraPosition = _camera.transform.position;
Vector3 objectPosition = transform.position;
// Calculate the point exactly on the screen edge that corresponds to the object's anchor
switch (anchorEdge)
{
case AnchorEdge.Top:
// Point on top edge with same X coordinate as the object
return new Vector3(
objectPosition.x,
cameraPosition.y + cameraOrthoSize,
objectPosition.z
);
case AnchorEdge.Bottom:
// Point on bottom edge with same X coordinate as the object
return new Vector3(
objectPosition.x,
cameraPosition.y - cameraOrthoSize,
objectPosition.z
);
case AnchorEdge.Left:
// Point on left edge with same Y coordinate as the object
return new Vector3(
cameraPosition.x - (screenWidth / 2f),
objectPosition.y,
objectPosition.z
);
case AnchorEdge.Right:
// Point on right edge with same Y coordinate as the object
return new Vector3(
cameraPosition.x + (screenWidth / 2f),
objectPosition.y,
objectPosition.z
);
default:
return objectPosition;
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ed380d10e1e04ae7990e5c726c929063
timeCreated: 1760359522

View File

@@ -0,0 +1,142 @@
using UnityEngine;
namespace AppleHillsCamera
{
/// <summary>
/// Defines reference sizes and distances for screen adaptation.
/// Used by CameraScreenAdapter and EdgeAnchor components.
/// </summary>
public class ScreenReferenceMarker : MonoBehaviour
{
[Header("Horizontal Reference")]
[Tooltip("The target width that should match the screen width")]
public float targetWidth = 10f;
[Header("Vertical References")]
[Tooltip("Distance from top of screen to use for anchoring")]
public float topMargin = 1f;
[Tooltip("Distance from bottom of screen to use for anchoring")]
public float bottomMargin = 1f;
[Tooltip("Distance from left of screen to use for anchoring")]
public float leftMargin = 1f;
[Tooltip("Distance from right of screen to use for anchoring")]
public float rightMargin = 1f;
[Header("Visualization")]
[Tooltip("Color to use for gizmo visualization")]
public Color gizmoColor = new Color(0f, 1f, 0f, 0.5f);
[Tooltip("Color to use for screen edge visualization")]
public Color screenEdgeColor = new Color(1f, 1f, 0f, 0.3f);
[Tooltip("Show the vertical margins in scene view")]
public bool showVerticalMargins = true;
[Tooltip("Show the horizontal margins in scene view")]
public bool showHorizontalMargins = true;
#if UNITY_EDITOR
private void OnDrawGizmos()
{
// Save original color
Color originalColor = Gizmos.color;
// Set the color for our gizmos
Gizmos.color = gizmoColor;
Vector3 position = transform.position;
// Draw the width reference
Vector3 left = position + Vector3.left * (targetWidth / 2f);
Vector3 right = position + Vector3.right * (targetWidth / 2f);
Gizmos.DrawLine(left, right);
// Draw vertical endpoints
float endCapSize = 0.5f;
Gizmos.DrawLine(left, left + Vector3.up * endCapSize);
Gizmos.DrawLine(left, left + Vector3.down * endCapSize);
Gizmos.DrawLine(right, right + Vector3.up * endCapSize);
Gizmos.DrawLine(right, right + Vector3.down * endCapSize);
// Calculate visual screen edges based on actual camera viewport
float halfWidth = targetWidth / 2f;
float halfHeight;
// Try to get camera references in the preferred order
// 1. Try to find the main camera in the scene (highest priority)
Camera mainCamera = Camera.main;
if (mainCamera != null && mainCamera.orthographic)
{
// Use the main camera's actual orthographic size for the height
halfHeight = mainCamera.orthographicSize;
}
else
{
// 2. Use Game/Simulator window resolution
float gameViewAspect = (float)Screen.height / Screen.width;
halfHeight = halfWidth * gameViewAspect;
// 3. Fallback to the scene view camera if needed
UnityEditor.SceneView sceneView = UnityEditor.SceneView.lastActiveSceneView;
if (sceneView != null && sceneView.camera != null && sceneView.camera.orthographic)
{
// Use the scene view camera's aspect ratio instead
float sceneAspect = sceneView.camera.pixelHeight / (float)sceneView.camera.pixelWidth;
halfHeight = halfWidth * sceneAspect;
}
}
// Screen edge positions
Vector3 topEdge = position + Vector3.up * halfHeight;
Vector3 bottomEdge = position + Vector3.down * halfHeight;
Vector3 leftEdge = position + Vector3.left * halfWidth;
Vector3 rightEdge = position + Vector3.right * halfWidth;
// Draw screen edges with yellow color
Gizmos.color = screenEdgeColor;
// Draw full screen rectangle
Gizmos.DrawLine(leftEdge + Vector3.up * halfHeight, rightEdge + Vector3.up * halfHeight); // Top edge
Gizmos.DrawLine(leftEdge + Vector3.down * halfHeight, rightEdge + Vector3.down * halfHeight); // Bottom edge
Gizmos.DrawLine(leftEdge + Vector3.up * halfHeight, leftEdge + Vector3.down * halfHeight); // Left edge
Gizmos.DrawLine(rightEdge + Vector3.up * halfHeight, rightEdge + Vector3.down * halfHeight); // Right edge
// Draw margin references if enabled
Gizmos.color = gizmoColor;
if (showVerticalMargins)
{
// Top margin (distance from top edge)
Gizmos.DrawLine(
topEdge + Vector3.down * topMargin + Vector3.left * (targetWidth / 4f),
topEdge + Vector3.down * topMargin + Vector3.right * (targetWidth / 4f));
// Bottom margin (distance from bottom edge)
Gizmos.DrawLine(
bottomEdge + Vector3.up * bottomMargin + Vector3.left * (targetWidth / 4f),
bottomEdge + Vector3.up * bottomMargin + Vector3.right * (targetWidth / 4f));
}
if (showHorizontalMargins)
{
// Left margin (distance from left edge)
Gizmos.DrawLine(
leftEdge + Vector3.right * leftMargin + Vector3.up * (halfHeight / 2f),
leftEdge + Vector3.right * leftMargin + Vector3.down * (halfHeight / 2f));
// Right margin (distance from right edge)
Gizmos.DrawLine(
rightEdge + Vector3.left * rightMargin + Vector3.up * (halfHeight / 2f),
rightEdge + Vector3.left * rightMargin + Vector3.down * (halfHeight / 2f));
}
// Restore original color
Gizmos.color = originalColor;
}
#endif
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 3058fe4801134fea916ad685f924668f
timeCreated: 1760359480

View File

@@ -15,11 +15,11 @@ namespace Cinematics
{
public event System.Action OnCinematicStarted;
public event System.Action OnCinematicStopped;
private static CinematicsManager _instance;
private static bool _isQuitting;
private Image cinematicSprites;
public PlayableAsset cinematicToPlay;
private Image _cinematicSprites;
private bool _isCinematicPlaying = false;
public bool IsCinematicPlaying => _isCinematicPlaying;
// Dictionary to track addressable handles by PlayableDirector
private Dictionary<PlayableDirector, AsyncOperationHandle<PlayableAsset>> _addressableHandles
@@ -69,20 +69,21 @@ namespace Cinematics
/// </summary>
public PlayableDirector PlayCinematic(PlayableAsset assetToPlay)
{
cinematicSprites.enabled = true;
_cinematicSprites.enabled = true;
playableDirector.stopped += OnPlayableDirectorStopped;
playableDirector.Play(assetToPlay);
Debug.Log("Playing cinematic " + assetToPlay.name);
_isCinematicPlaying = true;
OnCinematicStarted?.Invoke();
return playableDirector;
}
void OnPlayableDirectorStopped(PlayableDirector director)
{
cinematicSprites.enabled = false;
_cinematicSprites.enabled = false;
Debug.Log("Cinematic stopped!");
_isCinematicPlaying = false;
OnCinematicStopped?.Invoke();
// Release the addressable handle associated with this director
ReleaseAddressableHandle(director);
}
@@ -115,14 +116,6 @@ namespace Cinematics
playableDirector.Stop();
}
}
/// <summary>
/// Checks if a cinematic is currently playing
/// </summary>
public bool IsCinematicPlaying()
{
return playableDirector != null && playableDirector.state == PlayState.Playing;
}
/// <summary>
/// Releases the addressable handle associated with a specific PlayableDirector
@@ -152,7 +145,54 @@ namespace Cinematics
_addressableHandles.Clear();
}
private void Start()
private void Awake()
{
PlayStartCinematicOnGameLoad();
}
/// <summary>
/// Loads a cinematic asynchronously while showing a loading screen, then plays it
/// </summary>
/// <param name="key">The addressable key of the cinematic to load</param>
/// <returns>The PlayableDirector playing the cinematic</returns>
public async System.Threading.Tasks.Task<PlayableDirector> PlayCinematicWithLoadingScreen(string key)
{
Debug.Log($"[CinematicsManager] Preparing to load cinematic with loading screen: {key}");
// First, show the loading screen BEFORE creating any async operations
UI.LoadingScreenController.Instance.ShowLoadingScreen();
// Give the loading screen a frame to render
await System.Threading.Tasks.Task.Yield();
// Now create the load handle and track its progress
Debug.Log($"[CinematicsManager] Starting cinematic asset load: {key}");
AsyncOperationHandle<PlayableAsset> handle = Addressables.LoadAssetAsync<PlayableAsset>(key);
// Update the loading screen with the progress provider after the handle is created
UI.LoadingScreenController.Instance.ShowLoadingScreen(() => handle.PercentComplete);
// Wait for the loading to complete
var result = await handle.Task;
// Store the handle for later release
_addressableHandles[playableDirector] = handle;
Debug.Log($"[CinematicsManager] Cinematic loaded: {key}");
// Hide the loading screen
UI.LoadingScreenController.Instance.HideLoadingScreen();
// Important: Wait for the loading screen to be fully hidden before playing the cinematic
await UI.LoadingScreenController.Instance.WaitForLoadingScreenToHideAsync();
Debug.Log($"[CinematicsManager] Loading screen hidden, now playing cinematic: {key}");
// Play the cinematic
return PlayCinematic(result);
}
private async void PlayStartCinematicOnGameLoad()
{
if (!SceneManager.GetActiveScene().name.ToLower().Contains("mainmenu"))
{
@@ -161,14 +201,11 @@ namespace Cinematics
_instance = this;
if (!SceneManager.GetActiveScene().name.ToLower().Contains("mainmenu"))
{
return;
}
playableDirector = GetComponent<PlayableDirector>();
cinematicSprites = GetComponentInChildren<Image>(true);
LoadAndPlayCinematic("IntroSequence");
_cinematicSprites = GetComponentInChildren<Image>(true);
// Use the new method with loading screen instead of direct load
await PlayCinematicWithLoadingScreen("IntroSequence");
}
}
}

View File

@@ -25,6 +25,9 @@ namespace Cinematics
void OnEnable()
{
if (CinematicsManager.Instance.IsCinematicPlaying)
HandleCinematicStarted();
CinematicsManager.Instance.OnCinematicStarted += HandleCinematicStarted;
CinematicsManager.Instance.OnCinematicStopped += HandleCinematicStopped;
}
@@ -50,7 +53,7 @@ namespace Cinematics
void Update()
{
// Only process while cinematic is playing and we're holding
if (_isHolding && CinematicsManager.Instance.IsCinematicPlaying())
if (_isHolding && CinematicsManager.Instance.IsCinematicPlaying)
{
float holdTime = Time.time - _holdStartTime;
float progress = Mathf.Clamp01(holdTime / holdDuration);

View File

@@ -12,8 +12,7 @@ namespace Core
/// </summary>
public class SceneManagerService : MonoBehaviour
{
[SerializeField] private LoadingScreenController loadingScreen;
private LoadingScreenController _loadingScreen;
private static SceneManagerService _instance;
private static bool _isQuitting = false;
/// <summary>
@@ -49,6 +48,15 @@ namespace Core
private readonly Dictionary<string, AsyncOperation> _activeUnloads = new();
private const string BootstrapSceneName = "BootstrapScene";
void Start()
{
_loadingScreen = LoadingScreenController.Instance;
// Set up loading screen event handlers
SetupLoadingScreenEvents();
}
void Awake()
{
_instance = this;
@@ -64,9 +72,6 @@ namespace Core
}
}
#endif
// Set up loading screen event handlers
SetupLoadingScreenEvents();
// Ensure BootstrapScene is loaded at startup
var bootstrap = SceneManager.GetSceneByName(BootstrapSceneName);
if (!bootstrap.isLoaded)
@@ -77,10 +82,10 @@ namespace Core
private void SetupLoadingScreenEvents()
{
if (loadingScreen == null) return;
SceneLoadStarted += _ => loadingScreen.ShowLoadingScreen();
SceneLoadCompleted += _ => loadingScreen.HideLoadingScreen();
if (_loadingScreen == null) return;
SceneLoadStarted += _ => _loadingScreen.ShowLoadingScreen(() => GetAggregateLoadProgress());
SceneLoadCompleted += _ => _loadingScreen.HideLoadingScreen();
}
void OnApplicationQuit()
@@ -142,9 +147,9 @@ namespace Core
public async Task LoadScenesAsync(IEnumerable<string> sceneNames, IProgress<float> progress = null)
{
// Show loading screen at the start of multiple scene loading
if (loadingScreen != null)
if (_loadingScreen != null)
{
loadingScreen.ShowLoadingScreen();
_loadingScreen.ShowLoadingScreen();
}
int total = 0;
@@ -181,9 +186,9 @@ namespace Core
}
// Hide loading screen after all scenes are loaded
if (loadingScreen != null)
if (_loadingScreen != null)
{
loadingScreen.HideLoadingScreen();
_loadingScreen.HideLoadingScreen();
}
}
@@ -195,9 +200,9 @@ namespace Core
public async Task UnloadScenesAsync(IEnumerable<string> sceneNames, IProgress<float> progress = null)
{
// Show loading screen at the start of multiple scene unloading
if (loadingScreen != null)
if (_loadingScreen != null)
{
loadingScreen.ShowLoadingScreen();
_loadingScreen.ShowLoadingScreen();
}
int total = 0;
@@ -234,9 +239,9 @@ namespace Core
}
// Hide loading screen after all scenes are unloaded
if (loadingScreen != null)
if (_loadingScreen != null)
{
loadingScreen.HideLoadingScreen();
_loadingScreen.HideLoadingScreen();
}
}

View File

@@ -3,6 +3,8 @@ using UnityEngine.SceneManagement;
using System;
using Input;
using Settings;
using System.Collections;
using Minigames.DivingForPictures;
namespace Utility
{
@@ -34,10 +36,12 @@ namespace Utility
public event Action OnOrientationCorrect;
private GameObject promptInstance;
private ScreenOrientationRequirement requiredOrientation;
private bool orientationCorrect;
private Coroutine orientationCheckCoroutine;
private GameObject _promptInstance;
private ScreenOrientationRequirement _requiredOrientation;
private bool _orientationCorrect;
private Coroutine _orientationCheckCoroutine;
private bool _isDivingMinigame = false;
private Coroutine _continuousOrientationCheckCoroutine;
void Awake()
{
@@ -57,40 +61,13 @@ namespace Utility
}
}
private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
// Clean up any previous prompt/coroutine
// CleanupPromptAndCoroutine();
if (IsMainMenuScene(scene))
return;
requiredOrientation = orientationConfig != null ? orientationConfig.GetRequirementForScene(scene.name) : ScreenOrientationRequirement.Portrait;
orientationCorrect = IsOrientationCorrect();
if (!orientationCorrect)
{
InputManager.Instance.SetInputMode(InputMode.UI);
ShowPrompt();
orientationCheckCoroutine = StartCoroutine(OrientationCheckRoutine());
}
else
{
orientationCorrect = true;
OnOrientationCorrect?.Invoke();
}
}
private bool IsMainMenuScene(Scene scene)
{
// Adjust this logic if you have a different main menu scene name
return scene.name == "Main Menu" || scene.name == "MainMenu";
}
/// <summary>
/// Checks if the current device orientation matches the required orientation for the scene
/// </summary>
/// <returns>True if the orientation is correct, false otherwise</returns>
public bool IsOrientationCorrect()
{
switch (requiredOrientation)
switch (_requiredOrientation)
{
case ScreenOrientationRequirement.Portrait:
return Screen.orientation == ScreenOrientation.Portrait || Screen.orientation == ScreenOrientation.PortraitUpsideDown;
@@ -101,36 +78,126 @@ namespace Utility
}
}
/// <summary>
/// Checks if the current scene is the diving minigame scene
/// </summary>
/// <returns>True if the scene is "DivingForPictures", false otherwise</returns>
public bool IsDivingMinigameScene(Scene scene)
{
return scene.name == "DivingForPictures";
}
private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
// Clean up any previous prompt/coroutine
CleanupPromptAndCoroutine();
if (IsMainMenuScene(scene))
return;
_isDivingMinigame = IsDivingMinigameScene(scene);
_requiredOrientation = orientationConfig != null ? orientationConfig.GetRequirementForScene(scene.name) : ScreenOrientationRequirement.Portrait;
_orientationCorrect = IsOrientationCorrect();
if (!_orientationCorrect)
{
InputManager.Instance.SetInputMode(InputMode.UI);
ShowPrompt();
// If this is the diving minigame, start continuous orientation checking
if (_isDivingMinigame)
{
_continuousOrientationCheckCoroutine = StartCoroutine(ContinuousOrientationCheckRoutine());
}
else
{
_orientationCheckCoroutine = StartCoroutine(OrientationCheckRoutine());
}
}
else
{
_orientationCorrect = true;
OnOrientationCorrect?.Invoke();
// If this is the diving minigame, start continuous orientation checking
if (_isDivingMinigame)
{
_continuousOrientationCheckCoroutine = StartCoroutine(ContinuousOrientationCheckRoutine());
}
}
}
private System.Collections.IEnumerator OrientationCheckRoutine()
{
while (!IsOrientationCorrect())
{
yield return new WaitForSeconds(0.5f);
}
orientationCorrect = true;
_orientationCorrect = true;
OnOrientationCorrect?.Invoke();
}
private System.Collections.IEnumerator ContinuousOrientationCheckRoutine()
{
while (true)
{
// Wait for a short interval before checking again
yield return new WaitForSeconds(0.2f);
// Check if orientation is now incorrect
if (!IsOrientationCorrect())
{
// Pause the game using DivingGameManager
if (DivingGameManager.Instance != null)
{
DivingGameManager.Instance.Pause();
}
// Show the orientation prompt
ShowPrompt();
// Wait until orientation is correct again
while (!IsOrientationCorrect())
{
yield return new WaitForSeconds(0.2f);
}
// Hide the prompt when orientation is correct again
if (_promptInstance != null)
{
Destroy(_promptInstance);
_promptInstance = null;
}
// Resume the game using DivingGameManager
if (DivingGameManager.Instance != null)
{
DivingGameManager.Instance.DoResume();
}
}
}
}
private void ShowPrompt()
{
if (orientationPromptPrefab != null && promptInstance == null)
if (orientationPromptPrefab != null && _promptInstance == null)
{
promptInstance = Instantiate(orientationPromptPrefab);
DontDestroyOnLoad(promptInstance);
_promptInstance = Instantiate(orientationPromptPrefab);
DontDestroyOnLoad(_promptInstance);
}
}
private void HandleOrientationCorrect()
{
if (promptInstance != null)
if (_promptInstance != null)
{
Destroy(promptInstance);
promptInstance = null;
Destroy(_promptInstance);
_promptInstance = null;
}
if (orientationCheckCoroutine != null)
if (_orientationCheckCoroutine != null)
{
StopCoroutine(orientationCheckCoroutine);
orientationCheckCoroutine = null;
StopCoroutine(_orientationCheckCoroutine);
_orientationCheckCoroutine = null;
}
InputManager.Instance.SetInputMode(InputMode.Game);
@@ -138,22 +205,36 @@ namespace Utility
private void CleanupPromptAndCoroutine()
{
if (promptInstance != null)
if (_promptInstance != null)
{
Destroy(promptInstance);
promptInstance = null;
Destroy(_promptInstance);
_promptInstance = null;
}
if (orientationCheckCoroutine != null)
if (_orientationCheckCoroutine != null)
{
StopCoroutine(orientationCheckCoroutine);
orientationCheckCoroutine = null;
StopCoroutine(_orientationCheckCoroutine);
_orientationCheckCoroutine = null;
}
if (_continuousOrientationCheckCoroutine != null)
{
StopCoroutine(_continuousOrientationCheckCoroutine);
_continuousOrientationCheckCoroutine = null;
}
}
private bool IsMainMenuScene(Scene scene)
{
// Adjust this logic if you have a different main menu scene name
return scene.name == "Main Menu" || scene.name == "MainMenu";
}
void OnDestroy()
{
SceneManager.sceneLoaded -= OnSceneLoaded;
OnOrientationCorrect -= HandleOrientationCorrect;
CleanupPromptAndCoroutine();
}
void OnApplicationQuit()

View File

@@ -139,6 +139,10 @@ namespace AppleHills.Core.Settings
[Tooltip("Animation curve for the shrinking effect")]
[SerializeField] private AnimationCurve viewfinderShrinkCurve = AnimationCurve.EaseInOut(0, 1, 1, 0);
[Header("Photo Input")]
[Tooltip("Input mode for photo taking sequence")]
[SerializeField] private PhotoInputModes photoInputMode = PhotoInputModes.Tap;
[Tooltip("Padding factor to add space around the monster (1.0 = exact size, 1.2 = 20% extra)")]
[SerializeField] private float paddingFactor = 1.2f;
@@ -220,6 +224,7 @@ namespace AppleHills.Core.Settings
public float PaddingFactor => paddingFactor;
public float MaxSizePercent => maxSizePercent;
public float MinSizePercent => minSizePercent;
public PhotoInputModes PhotoInputMode => photoInputMode;
public override void OnValidate()
{

View File

@@ -3,6 +3,15 @@ using System.Collections.Generic;
namespace AppleHills.Core.Settings
{
/// <summary>
/// Enum defining the different input modes for photo taking
/// </summary>
public enum PhotoInputModes
{
Tap, // Original tap-based input
Hold // New hold-based input
}
/// <summary>
/// Interface for player and follower settings
/// </summary>
@@ -114,11 +123,12 @@ namespace AppleHills.Core.Settings
float ViewfinderShrinkDuration { get; }
float ViewfinderMoveSpeed { get; }
AnimationCurve ViewfinderShrinkCurve { get; }
float ViewfinderStartScale { get; }
float ViewfinderEndScale { get; }
float[] ViewfinderProgressThresholds { get; }
float PaddingFactor { get; }
float MaxSizePercent { get; }
float MinSizePercent { get; }
// Photo Input Settings
PhotoInputModes PhotoInputMode { get; }
}
}

View File

@@ -90,7 +90,7 @@ namespace Input
if (sceneName.ToLower().Contains("mainmenu"))
{
Debug.Log("[InputManager] SwitchInputOnSceneLoaded - Setting InputMode to UI for MainMenu");
SetInputMode(InputMode.UI);
SetInputMode(InputMode.GameAndUI);
}
else
{

View File

@@ -1,123 +1,129 @@
using System;
using AppleHills.Core.Settings;
using Core;
using Input;
using Interactions;
using UnityEngine;
using AppleHills.Core.Settings;
using Core; // Added for IInteractionSettings
/// <summary>
/// Handles level switching when interacted with. Applies switch data and triggers scene transitions.
/// </summary>
public class LevelSwitch : MonoBehaviour
// Added for IInteractionSettings
namespace LevelS
{
/// <summary>
/// Data for this level switch (target scene, icon, etc).
/// Handles level switching when interacted with. Applies switch data and triggers scene transitions.
/// </summary>
public LevelSwitchData switchData;
private SpriteRenderer _iconRenderer;
private Interactable _interactable;
public class LevelSwitch : MonoBehaviour
{
/// <summary>
/// Data for this level switch (target scene, icon, etc).
/// </summary>
public LevelSwitchData switchData;
private SpriteRenderer _iconRenderer;
private Interactable _interactable;
// Settings reference
private IInteractionSettings _interactionSettings;
// Settings reference
private IInteractionSettings _interactionSettings;
private bool _isActive = true;
private bool _isActive = true;
/// <summary>
/// Unity Awake callback. Sets up icon, interactable, and event handlers.
/// </summary>
void Awake()
{
_isActive = true;
if (_iconRenderer == null)
_iconRenderer = GetComponent<SpriteRenderer>();
_interactable = GetComponent<Interactable>();
if (_interactable != null)
/// <summary>
/// Unity Awake callback. Sets up icon, interactable, and event handlers.
/// </summary>
void Awake()
{
_interactable.characterArrived.AddListener(OnCharacterArrived);
_isActive = true;
if (_iconRenderer == null)
_iconRenderer = GetComponent<SpriteRenderer>();
_interactable = GetComponent<Interactable>();
if (_interactable != null)
{
_interactable.characterArrived.AddListener(OnCharacterArrived);
}
// Initialize settings reference
_interactionSettings = GameManager.GetSettingsObject<IInteractionSettings>();
ApplySwitchData();
}
// Initialize settings reference
_interactionSettings = GameManager.GetSettingsObject<IInteractionSettings>();
ApplySwitchData();
}
/// <summary>
/// Unity OnDestroy callback. Cleans up event handlers.
/// </summary>
void OnDestroy()
{
if (_interactable != null)
/// <summary>
/// Unity OnDestroy callback. Cleans up event handlers.
/// </summary>
void OnDestroy()
{
_interactable.characterArrived.RemoveListener(OnCharacterArrived);
if (_interactable != null)
{
_interactable.characterArrived.RemoveListener(OnCharacterArrived);
}
}
}
#if UNITY_EDITOR
/// <summary>
/// Unity OnValidate callback. Ensures icon and data are up to date in editor.
/// </summary>
void OnValidate()
{
if (_iconRenderer == null)
_iconRenderer = GetComponent<SpriteRenderer>();
ApplySwitchData();
}
/// <summary>
/// Unity OnValidate callback. Ensures icon and data are up to date in editor.
/// </summary>
void OnValidate()
{
if (_iconRenderer == null)
_iconRenderer = GetComponent<SpriteRenderer>();
ApplySwitchData();
}
#endif
/// <summary>
/// Applies the switch data to the level switch (icon, name, etc).
/// </summary>
public void ApplySwitchData()
{
if (switchData != null)
/// <summary>
/// Applies the switch data to the level switch (icon, name, etc).
/// </summary>
public void ApplySwitchData()
{
if (_iconRenderer != null)
_iconRenderer.sprite = switchData.mapSprite;
gameObject.name = switchData.targetLevelSceneName;
// Optionally update other fields, e.g. description
if (switchData != null)
{
if (_iconRenderer != null)
_iconRenderer.sprite = switchData.mapSprite;
gameObject.name = switchData.targetLevelSceneName;
// Optionally update other fields, e.g. description
}
}
}
/// <summary>
/// Handles the start of an interaction (shows confirmation menu and switches the level if confirmed).
/// </summary>
private void OnCharacterArrived()
{
if (switchData == null || string.IsNullOrEmpty(switchData.targetLevelSceneName) || !_isActive)
return;
/// <summary>
/// Handles the start of an interaction (shows confirmation menu and switches the level if confirmed).
/// </summary>
private void OnCharacterArrived()
{
if (switchData == null || string.IsNullOrEmpty(switchData.targetLevelSceneName) || !_isActive)
return;
var menuPrefab = _interactionSettings?.LevelSwitchMenuPrefab;
if (menuPrefab == null)
{
Debug.LogError("LevelSwitchMenu prefab not assigned in InteractionSettings!");
return;
}
// Spawn the menu overlay (assume Canvas parent is handled in prefab setup)
var menuGo = Instantiate(menuPrefab);
var menu = menuGo.GetComponent<LevelSwitchMenu>();
if (menu == null)
{
Debug.LogError("LevelSwitchMenu component missing on prefab!");
Destroy(menuGo);
return;
}
// Setup menu with data and callbacks
menu.Setup(switchData, OnMenuConfirm, OnMenuCancel);
_isActive = false; // Prevent re-triggering until menu is closed
var menuPrefab = _interactionSettings?.LevelSwitchMenuPrefab;
if (menuPrefab == null)
{
Debug.LogError("LevelSwitchMenu prefab not assigned in InteractionSettings!");
return;
}
// Spawn the menu overlay (assume Canvas parent is handled in prefab setup)
var menuGo = Instantiate(menuPrefab);
var menu = menuGo.GetComponent<LevelSwitchMenu>();
if (menu == null)
{
Debug.LogError("LevelSwitchMenu component missing on prefab!");
Destroy(menuGo);
return;
}
// Setup menu with data and callbacks
menu.Setup(switchData, OnMenuConfirm, OnMenuCancel);
_isActive = false; // Prevent re-triggering until menu is closed
// Switch input mode to UI only
InputManager.Instance.SetInputMode(InputMode.UI);
}
// Switch input mode to UI only
InputManager.Instance.SetInputMode(InputMode.UI);
}
private async void OnMenuConfirm()
{
var progress = new Progress<float>(p => Debug.Log($"Loading progress: {p * 100:F0}%"));
await SceneManagerService.Instance.SwitchSceneAsync(switchData.targetLevelSceneName, progress);
}
private async void OnMenuConfirm()
{
var progress = new Progress<float>(p => Debug.Log($"Loading progress: {p * 100:F0}%"));
await SceneManagerService.Instance.SwitchSceneAsync(switchData.targetLevelSceneName, progress);
}
private void OnMenuCancel()
{
_isActive = true; // Allow interaction again if cancelled
private void OnMenuCancel()
{
_isActive = true; // Allow interaction again if cancelled
InputManager.Instance.SetInputMode(InputMode.GameAndUI);
}
}
}

View File

@@ -294,6 +294,8 @@ namespace Minigames.DivingForPictures
private void SpawnMonster(Transform spawnPoint)
{
Debug.Log("Spawning monster: " + spawnPoint.name);
if (monsterPrefabs.Length == 0)
{
Debug.LogWarning("No monster prefabs assigned to DivingGameManager.");

View File

@@ -29,6 +29,8 @@ namespace Minigames.DivingForPictures
private void Awake()
{
Debug.Log("Monster created: " + gameObject.name);
if (detectionCollider == null)
detectionCollider = GetComponent<CircleCollider2D>();
@@ -44,6 +46,11 @@ namespace Minigames.DivingForPictures
photoSequenceInProgress = false;
}
private void OnDestroy()
{
Debug.Log("Monster destroyed: " + gameObject.name);
}
private IEnumerator CheckIfOffScreen()
{
WaitForSeconds wait = new WaitForSeconds(0.5f);
@@ -72,15 +79,24 @@ namespace Minigames.DivingForPictures
Vector3 worldPosition = transform.position;
Vector3 viewportPoint = mainCamera.WorldToViewportPoint(worldPosition);
// Adjust buffer to be larger below the screen to account for downward movement
float bufferSides = 0.2f;
float bufferTop = 0.2f;
float bufferBottom = 0.5f; // Larger buffer below the screen
// If z is negative, the object is behind the camera
if (viewportPoint.z < 0)
return false;
return viewportPoint.x > -bufferSides &&
viewportPoint.x < 1 + bufferSides &&
viewportPoint.y > -bufferBottom &&
viewportPoint.y < 1 + bufferTop;
// Simple logic:
// 1. Allow monsters below the screen (new spawns)
// 2. Despawn monsters only when completely above the top of screen
// 3. Keep monsters that are on screen
// Check if the monster is above the top of the screen
if (viewportPoint.y > 1)
return false; // Monster is completely above screen, destroy it
// Check horizontal bounds (keep moderate buffer so monsters don't disappear at screen edges)
float bufferSides = 0.2f;
bool withinHorizontalBounds = viewportPoint.x > -bufferSides && viewportPoint.x < 1 + bufferSides;
return withinHorizontalBounds;
}
private void OnTriggerEnter2D(Collider2D other)

View File

@@ -57,6 +57,9 @@ namespace Minigames.DivingForPictures.PictureCamera
// Store settings
private IDivingMinigameSettings settings;
// New field to store the current input mode
private PhotoInputModes currentInputMode;
// Events for progress milestones
public event Action<float> OnProgressUpdated; // Continuous progress updates (0-1)
@@ -89,6 +92,9 @@ namespace Minigames.DivingForPictures.PictureCamera
{
settings = GameManager.GetSettingsObject<IDivingMinigameSettings>();
mainCamera = UnityEngine.Camera.main;
// Get the photo input mode from settings
currentInputMode = settings.PhotoInputMode;
}
/// <summary>
@@ -135,7 +141,7 @@ namespace Minigames.DivingForPictures.PictureCamera
{
viewfinderComponent.Initialize(this);
viewfinderComponent.SetupInputOverride();
viewfinderComponent.OnViewfinderTapped += HandleViewfinderTapped;
InitializeViewfinder(viewfinderComponent);
}
// Reset state
@@ -450,12 +456,17 @@ namespace Minigames.DivingForPictures.PictureCamera
{
if (viewfinderInstance != null)
{
// Unsubscribe from the viewfinder's tap event
viewfinderComponent.RemoveInputOverride();
// Unsubscribe from all viewfinder events
if (viewfinderComponent != null)
{
viewfinderComponent.OnViewfinderTapped -= HandleViewfinderTapped;
viewfinderComponent.OnViewfinderTapped -= HandleViewfinderTappedDuringAnimation;
viewfinderComponent.OnViewfinderHoldStarted -= HandleViewfinderHoldStarted;
viewfinderComponent.OnViewfinderHoldEnded -= HandleViewfinderHoldEnded;
}
Destroy(viewfinderInstance);
viewfinderInstance = null;
viewfinderComponent = null;
@@ -507,8 +518,7 @@ namespace Minigames.DivingForPictures.PictureCamera
{
viewfinderComponent.Initialize(this);
viewfinderComponent.SetupInputOverride();
viewfinderComponent.OnViewfinderTapped += HandleViewfinderTapped;
InitializeViewfinder(viewfinderComponent);
}
// Determine canvas width for full-screen sizing
@@ -583,6 +593,20 @@ namespace Minigames.DivingForPictures.PictureCamera
viewfinderComponent.OnViewfinderTapped += HandleViewfinderTappedDuringAnimation;
}
// Initialize viewfinder with appropriate event handlers based on input mode
if (viewfinderComponent != null)
{
viewfinderComponent.Initialize(this);
viewfinderComponent.SetupInputOverride();
InitializeViewfinder(viewfinderComponent);
// For animation sequence, we need additional handling for tapping during animation
if (currentInputMode == PhotoInputModes.Tap)
{
viewfinderComponent.OnViewfinderTapped += HandleViewfinderTappedDuringAnimation;
}
}
// Reset state
animationProgress = 0f;
isAnimating = true;
@@ -763,7 +787,6 @@ namespace Minigames.DivingForPictures.PictureCamera
isReversePhase = false;
// Hide the viewfinder on the second tap
viewfinderComponent.RemoveInputOverride();
HideViewfinder();
}
else
@@ -772,5 +795,74 @@ namespace Minigames.DivingForPictures.PictureCamera
HandleViewfinderTapped();
}
}
/// <summary>
/// Initialize the viewfinder with appropriate event handlers based on input mode
/// </summary>
/// <param name="viewfinder">The viewfinder component to initialize</param>
public void InitializeViewfinder(Viewfinder viewfinder)
{
if (viewfinder != null)
{
// Clean up any existing event subscriptions
viewfinder.OnViewfinderTapped -= HandleViewfinderTapped;
viewfinder.OnViewfinderHoldStarted -= HandleViewfinderHoldStarted;
viewfinder.OnViewfinderHoldEnded -= HandleViewfinderHoldEnded;
// Set up handlers based on the current input mode
switch (currentInputMode)
{
case PhotoInputModes.Tap:
// For tap mode, only subscribe to tap events
viewfinder.OnViewfinderTapped += HandleViewfinderTapped;
break;
case PhotoInputModes.Hold:
// For hold mode, subscribe to hold start/end events
viewfinder.OnViewfinderHoldStarted += HandleViewfinderHoldStarted;
viewfinder.OnViewfinderHoldEnded += HandleViewfinderHoldEnded;
break;
}
}
}
/// <summary>
/// Handles hold start event from the viewfinder
/// </summary>
private void HandleViewfinderHoldStarted()
{
if (currentInputMode == PhotoInputModes.Hold)
{
// Start the photo sequence when hold begins (same behavior as first tap in tap mode)
OnViewfinderTapped?.Invoke();
Debug.Log("[CameraViewfinderManager] Hold started - initiating photo sequence");
}
}
/// <summary>
/// Handles hold end event from the viewfinder
/// </summary>
private void HandleViewfinderHoldEnded()
{
if (currentInputMode == PhotoInputModes.Hold && isAnimating)
{
// Complete the sequence when hold ends (same behavior as second tap in tap mode)
OnViewfinderTappedDuringAnimation?.Invoke(currentProximity);
Debug.Log("[CameraViewfinderManager] Hold ended - completing photo sequence with proximity: " + currentProximity);
// Complete the animation immediately
if (animationCoroutine != null)
{
StopCoroutine(animationCoroutine);
animationCoroutine = null;
}
// Fire completed event
OnAnimationCompleted?.Invoke();
isAnimating = false;
isReversePhase = false;
HideViewfinder();
}
}
}
}

View File

@@ -15,6 +15,8 @@ namespace Minigames.DivingForPictures.PictureCamera
// Events
public event System.Action OnViewfinderTapped;
public event System.Action OnViewfinderHoldStarted;
public event System.Action OnViewfinderHoldEnded;
// State
private bool isActive = true;
@@ -101,10 +103,30 @@ namespace Minigames.DivingForPictures.PictureCamera
}
}
// Required interface methods (no-op implementations)
public void OnHoldStart(Vector2 position) { }
public void OnHoldMove(Vector2 position) { }
public void OnHoldEnd(Vector2 position) { }
public void OnHoldStart(Vector2 position)
{
if (isActive)
{
// Fire the hold start event
OnViewfinderHoldStarted?.Invoke();
Debug.Log($"[MDPI] Viewfinder OnHoldStart: {position}");
}
}
public void OnHoldMove(Vector2 position)
{
// We can use this for any continuous effects during hold if needed in the future
}
public void OnHoldEnd(Vector2 position)
{
if (isActive)
{
// Fire the hold end event
OnViewfinderHoldEnded?.Invoke();
Debug.Log($"[MDPI] Viewfinder OnHoldEnd: {position}");
}
}
#endregion

View File

@@ -1,6 +1,7 @@
using UnityEngine;
using AppleHills.Core.Settings;
using Input;
using AppleHillsCamera;
namespace Minigames.DivingForPictures
{
@@ -10,6 +11,10 @@ namespace Minigames.DivingForPictures
/// </summary>
public class PlayerController : MonoBehaviour, ITouchInputConsumer
{
[Tooltip("Reference to the edge anchor that this player should follow for Y position")]
[SerializeField] private EdgeAnchor edgeAnchor;
// Settings reference
private IDivingMinigameSettings _settings;
@@ -42,6 +47,38 @@ namespace Minigames.DivingForPictures
_targetFingerX = transform.position.x;
_isTouchActive = false;
// Try to find edge anchor if not assigned
if (edgeAnchor == null)
{
// First try to find edge anchor on the same object or parent
edgeAnchor = GetComponentInParent<EdgeAnchor>();
// If not found, find any edge anchor in the scene
if (edgeAnchor == null)
{
edgeAnchor = FindObjectOfType<EdgeAnchor>();
if (edgeAnchor == null)
{
Debug.LogWarning("[PlayerController] No EdgeAnchor found in scene. Origin Y position won't update with camera changes.");
}
else
{
Debug.Log($"[PlayerController] Auto-connected to EdgeAnchor on {edgeAnchor.gameObject.name}");
}
}
}
// Subscribe to edge anchor events if it exists
if (edgeAnchor != null)
{
// Unsubscribe first to prevent duplicate subscriptions
edgeAnchor.OnPositionUpdated -= UpdateOriginYFromAnchor;
edgeAnchor.OnPositionUpdated += UpdateOriginYFromAnchor;
// Update origin Y based on current anchor position
UpdateOriginYFromAnchor();
}
DivingGameManager.Instance.OnGameInitialized += Initialize;
// If game is already initialized, initialize immediately
@@ -70,6 +107,12 @@ namespace Minigames.DivingForPictures
private void OnDestroy()
{
DivingGameManager.Instance.OnGameInitialized -= Initialize;
// Unsubscribe from edge anchor events
if (edgeAnchor != null)
{
edgeAnchor.OnPositionUpdated -= UpdateOriginYFromAnchor;
}
}
/// <summary>
@@ -185,5 +228,31 @@ namespace Minigames.DivingForPictures
}
transform.position = new Vector3(newX, newY, transform.position.z);
}
/// <summary>
/// Updates the origin Y position based on camera adjustments
/// </summary>
public void UpdateOriginY(float newOriginY)
{
_originY = newOriginY;
}
/// <summary>
/// Updates the origin Y position based on the current position of the player
/// This method is intended to be called by the camera adapter when the camera is adjusted.
/// </summary>
private void UpdateOriginYFromCurrentPosition()
{
_originY = transform.position.y;
}
/// <summary>
/// Updates the origin Y position based on the current position of the edge anchor
/// This method is intended to be called by the edge anchor when its position is updated.
/// </summary>
private void UpdateOriginYFromAnchor()
{
_originY = edgeAnchor.transform.position.y;
}
}
}

View File

@@ -16,12 +16,43 @@ public class RockFollower : MonoBehaviour
/// </summary>
public bool useWobbleOffset = true;
/// <summary>
/// The base Y position for the rock.
/// The vertical distance between the rock and the bottle.
/// </summary>
public float baseY = -6f;
[SerializeField] private float verticalDistance = 6f;
private float velocityX; // For SmoothDamp
#if UNITY_EDITOR
/// <summary>
/// Called in editor when properties are changed.
/// Updates the object's position when verticalDistance is modified.
/// </summary>
private void OnValidate()
{
// Only update position if playing or in prefab mode
if (Application.isPlaying || UnityEditor.PrefabUtility.IsPartOfPrefabAsset(this))
return;
if (bottleTransform != null)
{
// Calculate the new Y position based on bottle's position and the updated verticalDistance
float newY = bottleTransform.position.y - verticalDistance;
// Apply the wobble offset if enabled
if (useWobbleOffset && bottleWobble != null)
{
newY += bottleWobble.VerticalOffset;
}
// Update only the Y position, keeping X and Z unchanged
transform.position = new Vector3(transform.position.x, newY, transform.position.z);
// Mark the scene as dirty to ensure changes are saved
UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(UnityEngine.SceneManagement.SceneManager.GetActiveScene());
}
}
#endif
void Update()
{
if (bottleTransform == null) return;
@@ -33,8 +64,8 @@ public class RockFollower : MonoBehaviour
// Smoothly follow bottle's X with stiffer motion
float newX = Mathf.SmoothDamp(currentX, targetX, ref velocityX, 1f / followStiffness);
// Calculate Y position
float newY = baseY;
// Calculate Y position based on bottle's position and vertical distance
float newY = bottleTransform.position.y - verticalDistance;
if (useWobbleOffset && bottleWobble != null)
{
newY += bottleWobble.VerticalOffset;

View File

@@ -1,4 +1,5 @@
using System.Collections;
using System;
using UnityEngine;
using UnityEngine.UI;
using Core;
@@ -22,9 +23,52 @@ namespace UI
private Coroutine _progressCoroutine;
private bool _loadingComplete = false;
private bool _animationComplete = false;
private Action _onLoadingScreenFullyHidden;
private static LoadingScreenController _instance;
private static bool _isQuitting;
/// <summary>
/// Delegate for providing progress values from different sources
/// </summary>
public delegate float ProgressProvider();
/// <summary>
/// Current progress provider being used for the loading screen
/// </summary>
private ProgressProvider _currentProgressProvider;
/// <summary>
/// Default progress provider that returns 0 (or 1 if loading is complete)
/// </summary>
private float DefaultProgressProvider() => _loadingComplete ? 1f : 0f;
/// <summary>
/// Check if the loading screen is currently active
/// </summary>
public bool IsActive => loadingScreenContainer != null && loadingScreenContainer.activeSelf;
public static LoadingScreenController Instance
{
get
{
if (_instance == null && Application.isPlaying && !_isQuitting)
{
_instance = FindAnyObjectByType<LoadingScreenController>();
if (_instance == null)
{
var go = new GameObject("LoadingScreenController");
_instance = go.AddComponent<LoadingScreenController>();
}
}
return _instance;
}
}
private void Awake()
{
_instance = this;
if (loadingScreenContainer == null)
loadingScreenContainer = gameObject;
@@ -38,8 +82,16 @@ namespace UI
/// <summary>
/// Shows the loading screen and resets the progress bar to zero
/// </summary>
public void ShowLoadingScreen()
/// <param name="progressProvider">Optional delegate to provide progress values (0-1). If null, uses default provider.</param>
/// <param name="onComplete">Optional callback when loading screen is fully hidden</param>
public void ShowLoadingScreen(ProgressProvider progressProvider = null, Action onComplete = null)
{
// Store the completion callback
_onLoadingScreenFullyHidden = onComplete;
// Set the progress provider, use default if none provided
_currentProgressProvider = progressProvider ?? DefaultProgressProvider;
// Stop any existing progress coroutine
if (_progressCoroutine != null)
{
@@ -67,7 +119,7 @@ namespace UI
/// <summary>
/// Animates the progress bar at a steady pace over the minimum display time,
/// while also checking actual loading progress from SceneManagerService
/// while also checking actual loading progress from the current progress provider
/// </summary>
private IEnumerator AnimateProgressBar()
{
@@ -80,12 +132,8 @@ namespace UI
float elapsedTime = Time.time - startTime;
float steadyProgress = Mathf.Clamp01(elapsedTime / minimumDisplayTime);
// Get the actual loading progress from SceneManagerService
float actualProgress = 0f;
if (SceneManagerService.Instance != null)
{
actualProgress = SceneManagerService.Instance.GetAggregateLoadProgress();
}
// Get the actual loading progress from the current provider
float actualProgress = _currentProgressProvider();
// If loading is complete, actualProgress should be 1.0
if (_loadingComplete)
@@ -133,6 +181,10 @@ namespace UI
{
loadingScreenContainer.SetActive(false);
Debug.Log("[LoadingScreen] Animation AND loading complete, hiding screen");
// Invoke the callback when fully hidden
_onLoadingScreenFullyHidden?.Invoke();
_onLoadingScreenFullyHidden = null;
}
}
@@ -156,6 +208,10 @@ namespace UI
{
loadingScreenContainer.SetActive(false);
Debug.Log("[LoadingScreen] Animation already complete, hiding screen immediately");
// Invoke the callback when fully hidden
_onLoadingScreenFullyHidden?.Invoke();
_onLoadingScreenFullyHidden = null;
}
}
else
@@ -164,5 +220,35 @@ namespace UI
// The coroutine will handle hiding when animation completes
}
}
/// <summary>
/// Waits until the loading screen is fully hidden before continuing
/// </summary>
/// <returns>Task that completes when the loading screen is hidden</returns>
public System.Threading.Tasks.Task WaitForLoadingScreenToHideAsync()
{
var tcs = new System.Threading.Tasks.TaskCompletionSource<bool>();
// If the loading screen is not active, complete immediately
if (!IsActive)
{
tcs.SetResult(true);
return tcs.Task;
}
// Store existing callback to chain it
Action existingCallback = _onLoadingScreenFullyHidden;
// Set new callback
_onLoadingScreenFullyHidden = () => {
// Call existing callback if any
existingCallback?.Invoke();
// Complete the task
tcs.SetResult(true);
};
return tcs.Task;
}
}
}

View File

@@ -48,10 +48,10 @@ namespace UI
// Subscribe to scene loaded events
SceneManagerService.Instance.SceneLoadCompleted += SetPauseMenuByLevel;
#if UNITY_EDITOR
// Set initial state based on current scene
SetPauseMenuByLevel(SceneManager.GetActiveScene().name);
#if UNITY_EDITOR
// Initialize pause menu state
HidePauseMenu(false);
#endif

View File

@@ -20,7 +20,7 @@ MonoBehaviour:
tapMaxDistance: 0.2
tapDecelerationRate: 15
baseSpawnProbability: 0.2
maxSpawnProbability: 0.5
maxSpawnProbability: 0.8
probabilityIncreaseRate: 0.01
guaranteedSpawnTime: 30
spawnCooldown: 5
@@ -76,6 +76,7 @@ MonoBehaviour:
m_PreInfinity: 2
m_PostInfinity: 2
m_RotationOrder: 4
photoInputMode: 1
paddingFactor: 2
minSizePercent: 0.15
maxSizePercent: 1
@@ -85,4 +86,4 @@ MonoBehaviour:
- 0.25
- 0.5
- 0.75
- 1
- 1