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);
|
||||
}
|
||||
@@ -116,14 +117,6 @@ namespace Cinematics
|
||||
}
|
||||
}
|
||||
|
||||
/// <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
|
||||
/// </summary>
|
||||
@@ -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;
|
||||
if (_loadingScreen == null) return;
|
||||
|
||||
SceneLoadStarted += _ => loadingScreen.ShowLoadingScreen();
|
||||
SceneLoadCompleted += _ => loadingScreen.HideLoadingScreen();
|
||||
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;
|
||||
|
||||
// Settings reference
|
||||
private IInteractionSettings _interactionSettings;
|
||||
|
||||
private bool _isActive = true;
|
||||
|
||||
/// <summary>
|
||||
/// Unity Awake callback. Sets up icon, interactable, and event handlers.
|
||||
/// </summary>
|
||||
void Awake()
|
||||
public class LevelSwitch : MonoBehaviour
|
||||
{
|
||||
_isActive = true;
|
||||
if (_iconRenderer == null)
|
||||
_iconRenderer = GetComponent<SpriteRenderer>();
|
||||
_interactable = GetComponent<Interactable>();
|
||||
if (_interactable != null)
|
||||
/// <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;
|
||||
|
||||
private bool _isActive = true;
|
||||
|
||||
/// <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;
|
||||
|
||||
var menuPrefab = _interactionSettings?.LevelSwitchMenuPrefab;
|
||||
if (menuPrefab == null)
|
||||
/// <summary>
|
||||
/// Handles the start of an interaction (shows confirmation menu and switches the level if confirmed).
|
||||
/// </summary>
|
||||
private void OnCharacterArrived()
|
||||
{
|
||||
Debug.LogError("LevelSwitchMenu prefab not assigned in InteractionSettings!");
|
||||
return;
|
||||
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
|
||||
|
||||
// Switch input mode to UI only
|
||||
InputManager.Instance.SetInputMode(InputMode.UI);
|
||||
}
|
||||
// Spawn the menu overlay (assume Canvas parent is handled in prefab setup)
|
||||
var menuGo = Instantiate(menuPrefab);
|
||||
var menu = menuGo.GetComponent<LevelSwitchMenu>();
|
||||
if (menu == null)
|
||||
|
||||
private async void OnMenuConfirm()
|
||||
{
|
||||
Debug.LogError("LevelSwitchMenu component missing on prefab!");
|
||||
Destroy(menuGo);
|
||||
return;
|
||||
var progress = new Progress<float>(p => Debug.Log($"Loading progress: {p * 100:F0}%"));
|
||||
await SceneManagerService.Instance.SwitchSceneAsync(switchData.targetLevelSceneName, progress);
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
@@ -58,6 +58,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)
|
||||
public event Action OnAnimationStarted;
|
||||
@@ -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,10 +456,15 @@ 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);
|
||||
@@ -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
|
||||
|
||||
78
docs/camera_adaptation_readme.md
Normal file
78
docs/camera_adaptation_readme.md
Normal file
@@ -0,0 +1,78 @@
|
||||
# Camera Screen Adaptation System
|
||||
|
||||
This system helps adapt your 2D game's orthographic camera to different screen sizes, ensuring content is displayed consistently across various devices. It also provides tools for anchoring game objects to screen edges.
|
||||
|
||||
## Components
|
||||
|
||||
### 1. ScreenReferenceMarker
|
||||
|
||||
This component defines the target width and edge margins for your level content.
|
||||
|
||||
- **Target Width**: The desired width to be displayed on screen
|
||||
- **Edge Margins**: Distances from screen edges for anchoring objects
|
||||
- **Visualization**: Editor-only visualization of the reference sizes
|
||||
|
||||
### 2. CameraScreenAdapter
|
||||
|
||||
Automatically adjusts the camera's orthographic size to ensure the target width (defined by the ScreenReferenceMarker) matches the screen width.
|
||||
|
||||
- **Reference Marker**: Link to your ScreenReferenceMarker
|
||||
- **Adapt on Resolution Change**: Automatically update when screen resolution changes
|
||||
|
||||
### 3. EdgeAnchor
|
||||
|
||||
Anchors game objects to screen edges, maintaining consistent distance when the camera's orthographic size changes.
|
||||
|
||||
- **Anchor Edge**: Which edge to anchor to (Top, Bottom, Left, Right)
|
||||
- **Reference Marker**: Uses the same marker as the CameraScreenAdapter
|
||||
- **Additional Offset**: Extra distance from the edge margin
|
||||
- **Continuous Update**: Whether to update position every frame
|
||||
|
||||
### 4. CameraScreenVisualizer
|
||||
|
||||
Helper component for visualizing camera boundaries in the editor.
|
||||
|
||||
## Setup Instructions
|
||||
|
||||
### Basic Setup
|
||||
|
||||
1. **Create the Reference Marker:**
|
||||
- Add a new empty GameObject to your scene
|
||||
- Add the `ScreenReferenceMarker` component to it
|
||||
- Position it at the center of your target view
|
||||
- Set the `targetWidth` to match your level's ideal width
|
||||
- Adjust margin values as needed
|
||||
|
||||
2. **Configure the Camera:**
|
||||
- Add the `CameraScreenAdapter` component to your main camera
|
||||
- Reference the ScreenReferenceMarker you created
|
||||
- Optionally, add `CameraScreenVisualizer` for better editor visualization
|
||||
|
||||
3. **Anchor Game Objects:**
|
||||
- For any objects that should maintain position relative to screen edges:
|
||||
- Add the `EdgeAnchor` component
|
||||
- Set the desired anchor edge (e.g., Top)
|
||||
- Reference the same ScreenReferenceMarker
|
||||
- Adjust additional offset if needed
|
||||
|
||||
### Example: Top-Edge Anchoring
|
||||
|
||||
For objects that need to stay a fixed distance from the top of the screen:
|
||||
|
||||
1. Set up your ScreenReferenceMarker with an appropriate topMargin value
|
||||
2. Add EdgeAnchor to the object you want to anchor
|
||||
3. Set AnchorEdge to Top
|
||||
4. Adjust additionalOffset for fine-tuning
|
||||
|
||||
## Tips for Level Design
|
||||
|
||||
- Place the ScreenReferenceMarker at the center of your scene's critical content
|
||||
- Use the visual gizmos to see how your reference width relates to camera boundaries
|
||||
- Test your scene in different aspect ratios to ensure proper adaptation
|
||||
- For complex levels, you might want multiple reference markers for different sections
|
||||
|
||||
## Runtime Considerations
|
||||
|
||||
- The system automatically adapts when the screen size changes
|
||||
- The CameraScreenAdapter can be disabled if you need to temporarily override the automatic sizing
|
||||
- EdgeAnchor components can be disabled when their objects don't need to maintain edge alignment
|
||||
Reference in New Issue
Block a user