First steps with camera framing

This commit is contained in:
Michal Pikulski
2025-10-13 15:34:07 +02:00
parent aefff3d050
commit a55ba5e29c
14 changed files with 908 additions and 125 deletions

View File

@@ -0,0 +1,111 @@
using UnityEngine;
using Unity.Cinemachine;
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;
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.");
}
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 8a71a21143bd4f4992d08829084d1e3b
timeCreated: 1760359498

View File

@@ -0,0 +1,310 @@
using UnityEngine;
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
{
public enum AnchorEdge
{
Top,
Bottom,
Left,
Right
}
[Tooltip("Reference marker that defines the screen edges and margins")]
public ScreenReferenceMarker referenceMarker;
[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;
[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);
private Camera _camera;
private int _lastScreenWidth;
private int _lastScreenHeight;
private Vector3 _originalPosition;
private bool _initialized = false;
#if UNITY_EDITOR
private void OnDrawGizmos()
{
if (!showVisualization || referenceMarker == null)
return;
// Draw a line from the object to its anchor point
Vector3 anchorPoint = GetAnchorPoint();
// Save original color
Color originalColor = Gizmos.color;
// Draw line to anchor point
Gizmos.color = visualizationColor;
Gizmos.DrawLine(gameObject.transform.position, anchorPoint);
// Draw a small sphere at the anchor point
Gizmos.DrawSphere(anchorPoint, 0.1f);
// Restore original color
Gizmos.color = originalColor;
}
#endif
private void Awake()
{
_originalPosition = transform.position;
FindCamera();
_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;
}
// Adjust position immediately when enabled in editor
#if UNITY_EDITOR
if (!Application.isPlaying)
{
UpdatePosition();
}
#endif
}
private void Start()
{
if (adjustOnStart && Application.isPlaying)
{
UpdatePosition();
}
}
private void Update()
{
bool shouldUpdate = false;
// 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()
{
// 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.");
}
}
/// <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
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
transform.position = newPosition;
}
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)
{
// 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 newPosition = transform.position;
switch (anchorEdge)
{
case AnchorEdge.Top:
// Position from the top of the screen
newPosition.y = cameraPosition.y + cameraOrthoSize - margin;
break;
case AnchorEdge.Bottom:
// Position from the bottom of the screen
newPosition.y = cameraPosition.y - cameraOrthoSize + margin;
break;
case AnchorEdge.Left:
// Position from the left of the screen
newPosition.x = cameraPosition.x - (screenWidth / 2f) + margin;
break;
case AnchorEdge.Right:
// Position from the right of the screen
newPosition.x = cameraPosition.x + (screenWidth / 2f) - margin;
break;
}
return newPosition;
}
/// <summary>
/// Gets the anchor point on the edge for visualization
/// </summary>
private Vector3 GetAnchorPoint()
{
if (_camera == null)
{
FindCamera();
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 anchorPoint = transform.position;
switch (anchorEdge)
{
case AnchorEdge.Top:
// Anchor at top edge with same X as the object
anchorPoint.y = cameraPosition.y + cameraOrthoSize;
break;
case AnchorEdge.Bottom:
// Anchor at bottom edge with same X as the object
anchorPoint.y = cameraPosition.y - cameraOrthoSize;
break;
case AnchorEdge.Left:
// Anchor at left edge with same Y as the object
anchorPoint.x = cameraPosition.x - (screenWidth / 2f);
break;
case AnchorEdge.Right:
// Anchor at right edge with same Y as the object
anchorPoint.x = cameraPosition.x + (screenWidth / 2f);
break;
}
return anchorPoint;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ed380d10e1e04ae7990e5c726c929063
timeCreated: 1760359522

View 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
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 3058fe4801134fea916ad685f924668f
timeCreated: 1760359480