using UnityEngine;
namespace TheIsland.Visual
{
///
/// Forces a 2D sprite or UI element to always face the camera.
/// Attach to any GameObject that should billboard towards the main camera.
///
public class Billboard : MonoBehaviour
{
#region Configuration
[Header("Billboard Settings")]
[Tooltip("If true, locks the Y-axis rotation (sprite stays upright)")]
[SerializeField] private bool lockYAxis = true;
[Tooltip("If true, uses the main camera. Otherwise, assign a specific camera.")]
[SerializeField] private bool useMainCamera = true;
[Tooltip("Custom camera to face (only used if useMainCamera is false)")]
[SerializeField] private Camera targetCamera;
[Tooltip("Flip the facing direction (useful for some sprite setups)")]
[SerializeField] private bool flipFacing = false;
#endregion
#region Private Fields
private Camera _camera;
private Transform _cameraTransform;
#endregion
#region Unity Lifecycle
private void Start()
{
CacheCamera();
}
private void LateUpdate()
{
if (_cameraTransform == null)
{
CacheCamera();
if (_cameraTransform == null) return;
}
FaceCamera();
}
#endregion
#region Private Methods
private void CacheCamera()
{
_camera = useMainCamera ? Camera.main : targetCamera;
if (_camera != null)
{
_cameraTransform = _camera.transform;
}
}
private void FaceCamera()
{
if (lockYAxis)
{
// Only rotate around Y-axis (sprite stays upright)
Vector3 lookDirection = _cameraTransform.position - transform.position;
lookDirection.y = 0; // Ignore vertical difference
if (lookDirection != Vector3.zero)
{
Quaternion targetRotation = Quaternion.LookRotation(
flipFacing ? lookDirection : -lookDirection
);
transform.rotation = targetRotation;
}
}
else
{
// Full billboard - face camera completely
transform.rotation = flipFacing
? Quaternion.LookRotation(transform.position - _cameraTransform.position)
: _cameraTransform.rotation;
}
}
#endregion
#region Public Methods
///
/// Set a custom camera to face (disables useMainCamera).
///
public void SetTargetCamera(Camera camera)
{
useMainCamera = false;
targetCamera = camera;
_camera = camera;
_cameraTransform = camera?.transform;
}
///
/// Reset to use main camera.
///
public void UseMainCamera()
{
useMainCamera = true;
CacheCamera();
}
///
/// Configure billboard for UI elements (full facing, no Y-lock).
///
public void ConfigureForUI()
{
lockYAxis = false;
flipFacing = false;
}
///
/// Configure billboard for sprites (Y-axis locked, stays upright).
///
public void ConfigureForSprite()
{
lockYAxis = true;
flipFacing = false;
}
///
/// Set whether to lock Y-axis rotation.
///
public void SetLockYAxis(bool locked)
{
lockYAxis = locked;
}
#endregion
}
}