using UnityEngine; /// /// 전체화면 배경 이미지의 가로 위치를 결정한다. 크기는 같은 오브젝트의 AspectRatioFitter /// (HeightControlsWidth) 가 잡고, 이 컴포넌트는 "어디를 보여줄지"만 정한다. /// /// 왜 필요한가 (#795): /// 16:9 배경을 9:19.5 세로 화면에 세로 맞춤하면 가로가 3.16배로 넘쳐 좌우가 잘린다. /// 단순 중앙 크롭이면 화면 중앙에 오는 것은 원본의 정중앙뿐이라, 주인공이 왼쪽에 있는 /// 그림은 얼굴이 화면 밖으로 잘려 나간다. /// 그렇다고 pivot 으로 밀어두면, 이번엔 이미지가 화면보다 좁아지는 가로 단말에서 /// 한쪽에만 검은 여백이 생기고 반대쪽은 잘리는 비대칭이 된다. /// /// 해법: 원하는 초점(focusX)으로 밀되, 넘치는 양(overflow)을 넘어서까지는 밀지 않도록 clamp 한다. /// - 이미지가 화면보다 넓을 때 : 초점이 화면 중앙에 오도록 이동 (단, 여백이 생기기 직전까지만) /// - 이미지가 화면보다 좁을 때 : overflow 가 0 이므로 자동으로 정중앙 → 좌우 대칭 검은 여백 /// 즉 "여백은 항상 대칭, 잘림이 있을 때만 초점을 존중한다". /// /// 비용: 레이아웃이 바뀔 때만 계산한다(매 프레임 Update 없음). /// [ExecuteAlways] [RequireComponent(typeof(RectTransform))] [DisallowMultipleComponent] public class WLBackgroundFit : MonoBehaviour { [Tooltip("크롭이 생길 때 화면 중앙에 두고 싶은 원본 이미지의 가로 위치 (0 = 왼쪽 끝, 0.5 = 정중앙, 1 = 오른쪽 끝).")] [Range(0f, 1f)] public float focusX = 0.5f; [Tooltip("세로도 같은 규칙으로 맞춘다. 세로 맞춤(HeightControlsWidth) 에서는 항상 0 이라 보통 꺼둔다.")] public bool applyVertical = false; [Range(0f, 1f)] public float focusY = 0.5f; RectTransform _rt; RectTransform Rt => _rt != null ? _rt : (_rt = (RectTransform)transform); // 이 컴포넌트가 값을 "구동"한다고 표시한다. 이렇게 해야 해상도가 바뀔 때마다 // 씬이 dirty 로 표시되지 않고(에디터에서 저장 안 한 변경으로 오인되지 않고), // 인스펙터에서도 손으로 못 건드리게 잠긴다. AspectRatioFitter 등 레이아웃 // 컴포넌트가 쓰는 것과 같은 표준 방식이다. DrivenRectTransformTracker _tracker; void OnEnable() { _tracker.Clear(); _tracker.Add(this, Rt, DrivenTransformProperties.AnchoredPosition | DrivenTransformProperties.Pivot); Apply(); } void OnDisable() { _tracker.Clear(); } void OnRectTransformDimensionsChange() { Apply(); } #if UNITY_EDITOR void OnValidate() { if (isActiveAndEnabled) Apply(); } #endif /// 초점을 존중하되 여백이 생기지 않는 범위로 clamp 한 오프셋을 적용한다. public void Apply() { var rt = Rt; var parent = rt.parent as RectTransform; if (parent == null) return; // pivot 은 0.5 를 전제로 한다(중앙 기준 오프셋 계산). 다른 값이면 바로잡는다. if (rt.pivot != new Vector2(0.5f, 0.5f)) rt.pivot = new Vector2(0.5f, 0.5f); var pos = rt.anchoredPosition; pos.x = Offset(rt.rect.width, parent.rect.width, focusX); if (applyVertical) pos.y = Offset(rt.rect.height, parent.rect.height, focusY); else pos.y = 0f; if (pos != rt.anchoredPosition) rt.anchoredPosition = pos; } /// /// size = 이미지가 차지하는 길이, avail = 화면(부모) 길이, focus = 중앙에 두고 싶은 정규화 위치. /// 초점을 중앙에 두려면 size*(0.5-focus) 만큼 밀어야 하고, 넘치는 절반(overflow)까지만 허용된다. /// static float Offset(float size, float avail, float focus) { float overflow = Mathf.Max(0f, (size - avail) * 0.5f); return Mathf.Clamp(size * (0.5f - focus), -overflow, overflow); } }