Unity 해상도 대응 코드

using UnityEngine;
using UnityEngine.UI;
using GoogleMobileAds.Api;

[RequireComponent(typeof(CanvasScaler))]
public class CanvasResolutionController : MonoBehaviour
{
    private GoogleAdMobManager AdManager => GoogleAdMobManager.Instance;
    private CanvasScaler _CanvasScaler;

    private RectTransform safeAreaWrapper;
    private RectTransform uiContainer;
    private RectTransform adBannerSpace;
    private RectTransform contentArea;

    private const float TARGET_ASPECT = 1440f / 2560f; // 9:16

    // 캐싱
    private Rect lastSafeArea; 
    private Vector2 lastScreenSize;
    private bool lastAdStatus;
    // ------------------

    void Awake()
    {
        _CanvasScaler = GetComponent<CanvasScaler>();
        _CanvasScaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
        _CanvasScaler.referenceResolution = new Vector2(1440, 2560);
        _CanvasScaler.screenMatchMode = CanvasScaler.ScreenMatchMode.MatchWidthOrHeight;

        CreateUIHierarchy();

        if (AdManager != null)
            AdManager.OnBannerAdLoaded += ApplySafeLayout;
    }

    void Start() => ApplySafeLayout();

    void LateUpdate()
    {
        if (CheckForChanges())
        {
            ApplySafeLayout();
        }
    }

    /// <summary>
    /// 레이아웃 업데이트가 필요한 변화가 있는지 확인
    /// </summary>
    private bool CheckForChanges()
    {
        bool isChanged = false;

        // 1. 화면 해상도 체크
        if (Screen.width != lastScreenSize.x || Screen.height != lastScreenSize.y)
        {
            lastScreenSize = new Vector2(Screen.width, Screen.height);
            isChanged = true;
        }

        // 2. Safe Area 체크
        if (Screen.safeArea != lastSafeArea)
        {
            lastSafeArea = Screen.safeArea;
            isChanged = true;
        }

        // 3. 광고 로드 상태 체크
        bool currentAdStatus = (AdManager != null && AdManager.IsBannerLoaded);
        if (currentAdStatus != lastAdStatus)
        {
            lastAdStatus = currentAdStatus;
            isChanged = true;
        }

        return isChanged;
    }

    void CreateUIHierarchy()
    {
        safeAreaWrapper = CreateRectElement("Safe_Area_Wrapper", transform);
        uiContainer = CreateRectElement("UI_Container", safeAreaWrapper);
        adBannerSpace = CreateRectElement("AdBannerSpace", uiContainer);
        contentArea = CreateRectElement("ContentArea", uiContainer);

        var fitter = contentArea.gameObject.AddComponent<AspectRatioFitter>();
        fitter.aspectRatio = TARGET_ASPECT;
        fitter.aspectMode = AspectRatioFitter.AspectMode.FitInParent;
        fitter.enabled = true;
    }

    public void ApplySafeLayout()
    {
        if (safeAreaWrapper == null || contentArea == null) return;

        Rect safe = Screen.safeArea;
        float sw = Screen.width;
        float sh = Screen.height;

        // 1. Safe Area 적용
        safeAreaWrapper.anchorMin = new Vector2(safe.x / sw, safe.y / sh);
        safeAreaWrapper.anchorMax = new Vector2((safe.x + safe.width) / sw, (safe.y + safe.height) / sh);
        ResetOffsets(safeAreaWrapper);

        // 2. 광고 높이 계산
        float adHeightPx = lastAdStatus ? GetAdBannerHeightInPixels() : 0f;
        float adHeightNormalized = adHeightPx / safe.height;

        // 3. 광고 및 콘텐츠 영역 분할
        adBannerSpace.anchorMin = new Vector2(0, 1 - adHeightNormalized);
        adBannerSpace.anchorMax = new Vector2(1, 1);
        ResetOffsets(adBannerSpace);

        contentArea.anchorMin = Vector2.zero;
        contentArea.anchorMax = new Vector2(1, 1 - adHeightNormalized);
        ResetOffsets(contentArea);

        // 4. 동적 Match 수정
        UpdateCanvasMatch(safe.width, safe.height - adHeightPx);

        Debug.Log($"[LayoutUpdate] Match: {_CanvasScaler.matchWidthOrHeight}, AdHeight: {adHeightPx}");
    }

    private void UpdateCanvasMatch(float availableW, float availableH)
    {
        if (availableW <= 0 || availableH <= 0) return;

        float availableAspect = availableW / availableH;

        // 가로가 남는 환경(패드) -> 세로에 맞춤
        if (availableAspect > TARGET_ASPECT)
        {
            _CanvasScaler.matchWidthOrHeight = 1f;
        }
        else // 세로가 남는 환경(플립) -> 가로에 맞춤
        {
            _CanvasScaler.matchWidthOrHeight = 0f;
        }
    }

    private RectTransform CreateRectElement(string name, Transform parent)
    {
        GameObject obj = new GameObject(name, typeof(RectTransform));
        obj.transform.SetParent(parent, false);
        RectTransform rt = obj.GetComponent<RectTransform>();
        rt.anchorMin = Vector2.zero;
        rt.anchorMax = Vector2.one;
        ResetOffsets(rt);
        return rt;
    }

    private void ResetOffsets(RectTransform rt) => rt.offsetMin = rt.offsetMax = Vector2.zero;

    private float GetAdBannerHeightInPixels()
    {
        int widthInDp = MobileAds.Utils.GetDeviceSafeWidth();
        AdSize adaptiveSize = AdSize.GetCurrentOrientationAnchoredAdaptiveBannerAdSizeWithWidth(widthInDp);
        return (float)adaptiveSize.Height * Screen.dpi / 160f;
    }

    void OnDestroy()
    {
        if (AdManager != null)
            AdManager.OnBannerAdLoaded -= ApplySafeLayout;
    }
}

댓글 달기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

위로 스크롤