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:
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
|
||||
Reference in New Issue
Block a user