Merge branch 'main' into DamianBranch
This commit is contained in:
@@ -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}
|
||||
|
||||
3
Assets/Scripts/AppleHillsCamera.meta
Normal file
3
Assets/Scripts/AppleHillsCamera.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 95f834c9cbc34cff9490c5582d66e463
|
||||
timeCreated: 1760359480
|
||||
127
Assets/Scripts/AppleHillsCamera/CameraScreenAdapter.cs
Normal file
127
Assets/Scripts/AppleHillsCamera/CameraScreenAdapter.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8a71a21143bd4f4992d08829084d1e3b
|
||||
timeCreated: 1760359498
|
||||
549
Assets/Scripts/AppleHillsCamera/EdgeAnchor.cs
Normal file
549
Assets/Scripts/AppleHillsCamera/EdgeAnchor.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Scripts/AppleHillsCamera/EdgeAnchor.cs.meta
Normal file
3
Assets/Scripts/AppleHillsCamera/EdgeAnchor.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ed380d10e1e04ae7990e5c726c929063
|
||||
timeCreated: 1760359522
|
||||
142
Assets/Scripts/AppleHillsCamera/ScreenReferenceMarker.cs
Normal file
142
Assets/Scripts/AppleHillsCamera/ScreenReferenceMarker.cs
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3058fe4801134fea916ad685f924668f
|
||||
timeCreated: 1760359480
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.");
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user