Add a system for setting up "real" camera framing to work between devices (#26)
- In-level authoring utility to designate level bounds - Camera Adapter component to be placed on a level's camera to perform the adjustments - EdgeAnchor tool, which allows anchoring of game objects to the screen bounds Co-authored-by: Michal Pikulski <michal@foolhardyhorizons.com> Reviewed-on: #26
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
|
||||
@@ -1,123 +1,128 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user