2025-10-14 04:56:00 +00:00
|
|
|
|
using AppleHills.Core;
|
|
|
|
|
|
using UnityEngine;
|
|
|
|
|
|
using System;
|
2025-10-14 15:53:58 +02:00
|
|
|
|
using Core;
|
2025-10-14 04:56:00 +00:00
|
|
|
|
|
|
|
|
|
|
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,
|
2025-11-20 12:24:56 +01:00
|
|
|
|
Middle,
|
2025-10-14 04:56:00 +00:00
|
|
|
|
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;
|
|
|
|
|
|
|
2025-11-20 12:24:56 +01:00
|
|
|
|
[Header("Custom Anchor Point")]
|
|
|
|
|
|
[Tooltip("Optional: Use this child Transform's world position as the anchor point instead of calculated bounds")]
|
|
|
|
|
|
public Transform customAnchorPoint;
|
|
|
|
|
|
|
2025-10-14 04:56:00 +00:00
|
|
|
|
[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)
|
|
|
|
|
|
{
|
2025-10-14 15:53:58 +02:00
|
|
|
|
Logging.Debug($"Camera adjusted event received by {gameObject.name}, updating position");
|
2025-10-14 04:56:00 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 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
|
2025-10-28 10:08:49 +01:00
|
|
|
|
var adapters = FindObjectsByType<CameraScreenAdapter>(FindObjectsSortMode.None);
|
2025-10-14 04:56:00 +00:00
|
|
|
|
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;
|
2025-10-14 15:53:58 +02:00
|
|
|
|
Logging.Debug($"EdgeAnchor on {gameObject.name} auto-connected to CameraScreenAdapter on {cameraAdapter.gameObject.name}");
|
2025-10-14 04:56:00 +00:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// If no matching camera found, use the first one
|
|
|
|
|
|
cameraAdapter = adapters[0];
|
2025-10-14 15:53:58 +02:00
|
|
|
|
Logging.Debug($"EdgeAnchor on {gameObject.name} auto-connected to CameraScreenAdapter on {cameraAdapter.gameObject.name}");
|
2025-10-14 04:56:00 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
{
|
2025-10-14 15:53:58 +02:00
|
|
|
|
Logging.Debug($"{gameObject.name}: Camera ortho size changed from {_lastOrthoSize} to {_camera.orthographicSize}");
|
2025-10-14 04:56:00 +00:00
|
|
|
|
}
|
|
|
|
|
|
_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)
|
|
|
|
|
|
{
|
2025-10-14 15:53:58 +02:00
|
|
|
|
Logging.Debug($"{gameObject.name}: Camera reference updated to {_camera.name}");
|
2025-10-14 04:56:00 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2025-11-20 12:24:56 +01:00
|
|
|
|
/// Get the combined bounds of all renderers on this object and its children.
|
|
|
|
|
|
/// If customAnchorPoint is set, returns a zero-size bounds at the anchor point's world position.
|
2025-10-14 04:56:00 +00:00
|
|
|
|
/// </summary>
|
|
|
|
|
|
private Bounds GetObjectBounds()
|
|
|
|
|
|
{
|
2025-11-20 12:24:56 +01:00
|
|
|
|
// If custom anchor point is specified, use its world position as the bounds center
|
|
|
|
|
|
if (customAnchorPoint != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Return zero-size bounds centered at the custom anchor point
|
|
|
|
|
|
// This makes the anchor point the exact position that will snap to the edge
|
|
|
|
|
|
Bounds customBounds = new Bounds(customAnchorPoint.position, Vector3.zero);
|
|
|
|
|
|
_objectBounds = customBounds;
|
|
|
|
|
|
return customBounds;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Default behavior: calculate bounds from renderers
|
2025-10-14 04:56:00 +00:00
|
|
|
|
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)
|
|
|
|
|
|
{
|
2025-10-14 15:53:58 +02:00
|
|
|
|
Logging.Warning("EdgeAnchor: Missing reference marker.");
|
2025-10-14 04:56:00 +00:00
|
|
|
|
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)
|
|
|
|
|
|
{
|
2025-10-14 15:53:58 +02:00
|
|
|
|
Logging.Debug($"{gameObject.name} position updated: {transform.position} -> {newPosition}, Camera OrthoSize: {_camera.orthographicSize}");
|
2025-10-14 04:56:00 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
2025-11-20 12:24:56 +01:00
|
|
|
|
case AnchorEdge.Middle:
|
|
|
|
|
|
return 0f; // Middle has no margin
|
2025-10-14 04:56:00 +00:00
|
|
|
|
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;
|
2025-11-20 12:24:56 +01:00
|
|
|
|
case AnchorEdge.Middle:
|
|
|
|
|
|
// For middle, no offset needed - object centers on middle
|
|
|
|
|
|
offsetY = -centerOffset.y;
|
|
|
|
|
|
break;
|
2025-10-14 04:56:00 +00:00
|
|
|
|
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;
|
|
|
|
|
|
|
2025-11-20 12:24:56 +01:00
|
|
|
|
case AnchorEdge.Middle:
|
|
|
|
|
|
// Position at the vertical center of the screen
|
|
|
|
|
|
newPosition.y = cameraPosition.y + offsetY;
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
2025-10-14 04:56:00 +00:00
|
|
|
|
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
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2025-11-20 12:24:56 +01:00
|
|
|
|
case AnchorEdge.Middle:
|
|
|
|
|
|
// Point at vertical center with same X coordinate as the object
|
|
|
|
|
|
return new Vector3(
|
|
|
|
|
|
objectPosition.x,
|
|
|
|
|
|
cameraPosition.y,
|
|
|
|
|
|
objectPosition.z
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2025-10-14 04:56:00 +00:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|