블로그 이미지
Every unexpected event is a path to learning for you.

카테고리

분류 전체보기 (2729)
Unity3D (813)
Programming (474)
Server (33)
Unreal (4)
Gamebryo (56)
Tip & Tech (227)
협업 (57)
3DS Max (3)
Game (12)
Utility (136)
Etc (96)
Link (32)
Portfolio (19)
Subject (90)
iOS,OSX (51)
Android (14)
Linux (5)
잉여 프로젝트 (2)
게임이야기 (3)
Memories (20)
Interest (38)
Thinking (38)
한글 (30)
PaperCraft (5)
Animation (408)
Wallpaper (2)
재테크 (18)
Exercise (3)
나만의 맛집 (3)
냥이 (10)
육아 (16)
Total
Today
Yesterday
03-19 00:10
using UnityEngine;

public class OcclusionCulling2D : MonoBehaviour
{
    [System.Serializable] public class ObjectSettings
    {
        [HideInInspector] public string title;
        public GameObject theGameObject;

        public Vector2 size = Vector2.one;
        public Vector2 offset = Vector2.zero;
        public bool multiplySizeByTransformScale = true;

        public Vector2 sized { get; set; }
        public Vector2 center { get; set; }
        public Vector2 TopRight { get; set; }
        public Vector2 TopLeft { get; set; }
        public Vector2 BottomLeft { get; set; }
        public Vector2 BottomRight { get; set; }
        public float right { get; set; }
        public float left { get; set; }
        public float top { get; set; }
        public float bottom { get; set; }

        public Color DrawColor = Color.white;
        public bool showBorders = true;
    }

    public ObjectSettings[] objectSettings = new ObjectSettings[1];

    private Camera camera;
    private float cameraHalfWidth;

    public float updateRateInSeconds = 0.1f;

    private float timer;

    void Awake(){ 
        camera = GetComponent<Camera>();
        cameraHalfWidth = camera.orthographicSize * ((float)Screen.width / (float)Screen.height);

        foreach(ObjectSettings o in objectSettings){
           o.sized = o.size * (o.multiplySizeByTransformScale ? new Vector2(Mathf.Abs(o.theGameObject.transform.localScale.x), Mathf.Abs(o.theGameObject.transform.localScale.y)) : Vector2.one);
            o.center = (Vector2)o.theGameObject.transform.position + o.offset;

            o.TopRight = new Vector2(o.center.x + o.sized.x, o.center.y + o.sized.y);
            o.TopLeft = new Vector2(o.center.x - o.sized.x, o.center.y + o.sized.y);
            o.BottomLeft = new Vector2(o.center.x - o.sized.x, o.center.y - o.sized.y);
            o.BottomRight = new Vector2(o.center.x + o.sized.x, o.center.y - o.sized.y);

            o.right = o.center.x + o.sized.x;
            o.left = o.center.x - o.sized.x;
            o.top = o.center.y + o.sized.y;
            o.bottom = o.center.y - o.sized.y;
        }
    }

    void OnDrawGizmosSelected()
    {
        foreach(ObjectSettings o in objectSettings)
        {
            if(o.theGameObject)
            {
                o.title = o.theGameObject.name;

                if(o.showBorders)
                {
                    o.TopRight = new Vector2(o.center.x + o.sized.x, o.center.y + o.sized.y);
                    o.TopLeft = new Vector2(o.center.x - o.sized.x, o.center.y + o.sized.y);
                    o.BottomLeft = new Vector2(o.center.x - o.sized.x, o.center.y - o.sized.y);
                    o.BottomRight = new Vector2(o.center.x + o.sized.x, o.center.y - o.sized.y);
                    Gizmos.color = o.DrawColor;
                    Gizmos.DrawLine(o.TopRight, o.TopLeft);
                    Gizmos.DrawLine(o.TopLeft, o.BottomLeft);
                    Gizmos.DrawLine(o.BottomLeft, o.BottomRight);
                    Gizmos.DrawLine(o.BottomRight, o.TopRight);
                }
            }
        }
    }
    
    void FixedUpdate()
    {
        timer += Time.deltaTime;
        if(timer > updateRateInSeconds) timer = 0;
        else return;

        float cameraRight = camera.transform.position.x + cameraHalfWidth;
        float cameraLeft = camera.transform.position.x - cameraHalfWidth;
        float cameraTop = camera.transform.position.y + camera.orthographicSize;
        float cameraBottom = camera.transform.position.y - camera.orthographicSize;

        foreach(ObjectSettings o in objectSettings)
        {
            if(o.theGameObject)
            {
                bool IsObjectVisibleInCastingCamera = o.right > cameraLeft & o.left < cameraRight & // check horizontal
                                                      o.top > cameraBottom & o.bottom < cameraTop; // check vertical
                o.theGameObject.SetActive(IsObjectVisibleInCastingCamera);
            }
        }
    }
}

[출처] https://www.youtube.com/watch?v=hbBDqdoHUpE

반응형
Posted by blueasa
, |

Unity 2021.3.36f1

Xcode 15.3

----

 

[결론] 

Privacy Manifest 관련 대응 버전은 Unity 2023.2.13, 2022.3.21, 2021.3.36 이라고 한다.(메이저 버전별 해당 버전이후로 업데이트 필요)

위에 적힌 버전 이상 설치 돼 있으면, iOS 빌드를 할 때 알아서 PrivacyInfo.xcprivacy를 생성해 준다고 한다.

Unity 2021.3.36f1으로 iOS 빌드했는데, PrivacyInfo.xcprivacy도 추가돼있고, 내용도 제대로 들어있는 걸 확인했다.(필요한 거 확인해서 알아서 넣어준다고 어디선가 적힌걸 본 것 같은데..)

Unity 2021.3.36f1으로 iOS 빌드하면 PrivacyInfo.xcprivacy가 자동 생성된다.

 

쉽게 해결하려면 엔진 버전업 하자.

 

SDK 등 Third Party 플러그인들은 해당 Third Party에서 업데이트 대응해줘야 되는거니 내가 뭔가 할 건 없는 것 같다.

 

P.s. 

수동으로 작업하려면 PolicyInfo.xcprivacy 파일을 생성해서 수정하고, ../Assets/Plugins 폴더에 파일을 넣어두면 된다는데..

버전업하면 자동으로 처리되는데 굳이 수작업 할 일이 있을까 싶다.

 

[참조] https://forum.unity.com/threads/apple-privacy-manifest-updates-for-unity-engine.1529026/

 

Official - Apple privacy manifest updates for Unity Engine

Introduction At WWDC 2023 Apple announced a number of additions to its privacy program, including the introduction of Privacy Manifests. Since then,...

forum.unity.com

 

----

[링크1] [Unity] Apple Privacy Manifest 대응

 

[Unity] Apple Privacy Manifest 대응

안녕하세요. Apple이 공개한 Privacy Manifest를 반드시 포함해야 하는 SDK 목록 중에 UnityFramework가 있었고 이번에 Unity에서 공식적인 입장과 가이드를 공개했어요. https://forum.unity.com/threads/apple-privacy-man

phillip5094.tistory.com

 

[링크2] 개인정보 보호 매니페스트 및 서명을 필요로 하는 SDK

 

개인정보 보호 매니페스트 및 서명을 필요로 하는 SDK

안녕하세요. 이전에 Privacy manifest에 대해서 공부했는데요. [WWDC23] Get started with privacy manifests 안녕하세요. 이번엔 WWDC23 'Get started with privacy manifests' 세션을 보고 내용 정리해 볼게요. #개요 앱 사용

phillip5094.tistory.com

 

 

[참조] 【Xcode/iOS】Privacy Manifests에 대응하는 방법! PrivacyInfo.xcprivacy란?

 

Webエンジニア学習部屋

駆け出しwebエンジニアのあめの学習記録webサイトです。WordPressなどのCMSを使わずに自分でHTMLファイルやphpファイルを作成しサーバーにアップしています。プログラミングで悩んだポイントや

appdev-room.com

 

 

 

 

반응형
Posted by blueasa
, |

Unity 2021.3.35f1

GoogleMobileAds 8.7.0

----

 

작년 10월쯤부터 ANR이 갑자기 많아져서 뭘까 했는데, 이제는 커트라인까지 넘어서 경고가 뜨고 있다.

그래서 ANR쪽 로그를 보니 아래와 같은 로그가 뜬다.

 

[ANR 로그]

com.google.android.ump:user-messaging-platform@@2.1.0 
- com.google.android.ump.ConsentInformation$OnConsentInfoUpdateSuccessListener.onConsentInfoUpdateSuccess
Input dispatching timed out

 

해당 ANR이 생긴 시점에 업데이트 된 GoogleMobileAds 버전을 보니 8.4.1이다.

GoogleMobileAds 8.4.1버전부터 현재 기준 최신인 8.7.0까지 같은 문제가 아직도 고쳐지지 않고 여전히 있는 것 같다.

 

UMP면 GoogleMoblieAds인데..

인터넷 검색해보니 아래 링크처럼 똑같은 문제를 겪는 사람들이 많다.

 

[ANRs Caused by UMP 2.1.0 Callbacks] https://groups.google.com/g/google-admob-ads-sdk/c/jrOwbA7lgdQ

 

ANRs Caused by UMP 2.1.0 Callbacks

Hi Nick, I am using  Unity 2021.3.34, Google Mobile Ads Unity Plugin v8.6.0, Gradle version 6.7.1, for resolve dependencies EDM4U. Regards, Alex. вторник, 16 января 2024 г. в 21:02:33 UTC+2, Mobile Ads SDK Forum Advisor:

groups.google.com

 

링크에는 최근까지 글이 올라오고 있는데, 작년부터 있던 문제가 현재도 고쳐지지 않고 있는 것 같다.

결국 GoogleMobileAds에서 직접 수정한 버전이 올라와야 이 문제는 끝날 것 같다.

 

요즘은 Unity도, Xcode(Apple)도, Google(Firebase/GoogleMobileAds)도 다들 뭔가..

나사가 빠져 있는 것 같다.

 

 

 

 
반응형
Posted by blueasa
, |

Unity 2021.3.33f1

----

 

[추가]

에디터 플랫폼(Android/iOS) 별로 체크되는 파일이 다른 것 같다.

Editor - iOS 에디터에서 AssetDatabase.ForceReserializeAssets();를 했는데,

Editor - Android 에디터에서 변경 안된 것들이 있어서 Android 플랫폼 에디터에서 한 번 더 Reserialize를 했다.

양 쪽 플랫폼에서 한 번씩 돌려야되나.. 싶다.

 

----

프로젝트 개발하면서 유니티 메이저 버전 업그레이드나 다운그레이드를 하게 될 경우가 있는데,

이번에는 Unity 2022의 누수 및 크래시 버그가 심각해서 Unity 2021로 내려온 후에 알 수 없는 버그가 종종 나오게 됐다.

(잘되던게 UI 하나 변경했더니 다른 팀원 유니티에서 이상하게 뜬다던지..)

 

확인해본 바로는 meta 파일은 실제 에셋을 사용하는 시점에만 갱신해서

이미 Unity 2021인데도 Unity 2022의 메타파일을 사용하고 있다가, 수정하게 되면서 Unity 2021 meta 파일로 변경되면서 라이브러리가 꼬이는 것 같다.

Unity 메이저 버전이 바뀌면 meta 파일을 재정리를 좀 해주면 좋을텐데 안하는 듯..

 

그래서 프로젝트 전체를 강제로 Reserialize를 진행했다.

AssetDatabase.ForceReserializeAssets();

 

 

[참조] https://docs.unity3d.com/ScriptReference/AssetDatabase.ForceReserializeAssets.html

 

Unity - Scripting API: AssetDatabase.ForceReserializeAssets

When Unity loads old data from an asset or Scene file, the data is dynamically upgraded in memory, but not written back to disk unless the user does something that explicitly dirties the object (like changing a value on it). This method allows you to proac

docs.unity3d.com

 

 

 

반응형
Posted by blueasa
, |

Unity 2021.3.35f1

GoogleMobileAds 8.7.0

AppsFlyer 6.13.0

----

 

이번에 GDPR 동의 관련 작업을 하게 되면서 이런저런 엮이는 것들이 많았는데..

문제가 됐던 이슈 리스트 정리해 봄

 

1) GDPR 동의를 추가하고나니 iOS의 IDFA(AppTrackingTransparency) 동의와 내용이 겹친다고

    "앞에서 거부했는데 왜 뒤에 또 동의창을 뛰우냐~" 라는 내용으로 iOS 검수 리젝 됨.

     - 참고로 iOS ATT(IDFA)와 GDPR의 순서는 GDPR먼저 뛰우고 ATT(IDFA)를 판단하라고 함

https://developers.google.com/admob/android/privacy/gdpr?hl=ko

 

    그래서 확인해보니, Admob(GoogleMobileAds)에서 GDPR과 IDFA 둘 다 제공하고 Admob에서 진행하면 알아서 유기적으로 처리해준다.

    - [EEA( European Economic Area)인 경우] iOS에서 GDPR 동의 거부하면 IDFA 동의창 안 뜸, GDPR 동의하면 IDFA 동의 창 뜸.

    - [EEA( European Economic Area)가 아닌 경우] iOS에서 IDFA 안내 및 동의창만 뜸.

 

[참고] https://blueasa.tistory.com/2789

 

[검수리젝] GDPR/IDFA(ATT) 관련 검수 리젝

Unity 2021.3.35f1 GoogleMobileAds 8.7.0 ---- GDPR 동의 로직 추가하고 iOS 검수 넣었더니 리젝 됐다. 사유는 대충 정리하면, GDPR 팝업에서 '거부'를 했는데, 같은 이슈인 'IDFA(AppTrackingTransparency)' 동의 여부를

blueasa.tistory.com

 

2) AppsFlyer도 이번에 버전업(6.13.0) 해서 보니, GDPR 관련 설정 옵션(AppsFlyerConsent 클래스가 생김)이 생겼다.

    아래 예시와 같이 GDPR 정보 셋팅 후, AppsFlyer를 Initialize 하라고 한다.

// If the user is subject to GDPR - collect the consent data
    // or retrieve it from the storage
    ...
    // Set the consent data to the SDK:
    AppsFlyerConsent consent = AppsFlyerConsent.ForGDPRUser(true, true);
    AppsFlyer.setConsentData(consent);
        
    AppsFlyer.startSDK();

 

    자세한 내용은 아래 AppsFlyer 문서를 참고하자.

[참고] https://ko.dev.appsflyer.com/hc/docs/basicintegration

 

연동

AppsFlyerObject 프리팹을 사용하거나 수동으로 플러그인을 초기화할 수 있습니다. AppsFlyerObject.prefabManual의 integrationCollect IDFA를 ATTrackingManagerSending SKAN 포스트백과 함께 사용하여 AppsflyerMacOS initializat

ko.dev.appsflyer.com

 

3) 위 AppsFlyer 연동하려고 보니 아래와 같이 GDPR 일 때, 넘겨주는 매개변수 2개가 보인다.

    - hasConsentForDataUsage : GDPR 동의 상태

    - hasConsentForAdsPersonaliation : 광고 개인화 동의 상태

public static AppsFlyerConsent ForGDPRUser(bool hasConsentForDataUsage, bool hasConsentForAdsPersonalization)
{
    return new AppsFlyerConsent(true, hasConsentForDataUsage, hasConsentForAdsPersonalization);
}

public static AppsFlyerConsent ForNonGDPRUser()
{
    return new AppsFlyerConsent(false, false, false);
}

 

- hasConsentForDataUsage는 GDPR 동의 상태라서 Admob에도 정보가 있어서 받아오면 되겠는데,

- hasConsentForAdsPersonaliation은 GDPR 동의 창 세부 상태에서 광고 개인화 동의를 체크를 하는데 정보를 어떻게 받는지 몰라서 이리저리 찾아보다가 IABTCF_VendorConsents가 광고 개인화 관련 동의 값이란 걸 알게 됐다.

 

[참고] https://stackoverflow.com/questions/69307205/mandatory-consent-for-admob-user-messaging-platform

 

Mandatory Consent for Admob User Messaging Platform

I switched from the deprecated GDPR Consent Library to the new User Messaging Platform, and used the code as stated in the documentation. I noticed that when the user clicks on Manage Options then

stackoverflow.com

 

관련해서 잘 정리해 놓은 글을 찾아서 블로그에도 올려뒀다.

[링크] https://blueasa.tistory.com/2791

 

[펌] GDPR에 대해 알아보자 (Feat 애드몹)

1 얘들아 안녕, 다들 개발 잘 하고 있어? 저번 달에 GDPR 구현하다가 막혀서 여기다 글을 올렸었는데, 다행히 이젠 해결한 것 같아. 그 당시 해결하면 팁 남겨달라던 친구의 댓글이 기억나서 다시

blueasa.tistory.com

 

 

위의 링크글에서 동의 정보 관련에 필요한 CurrentGDPR 클래스 설명이 잘 돼 있다.

원래 CurrentGDPR 클래스는 GoogleMobileAds 8.6.0 이전 버전 기준으로 만들어져서,

GoogleMobileAds 8.7.0에 추가된 ApplicationPreferences를 사용해서 OS에 따라 분기하지 않아도 되도록 수정해서 아래 올려둔다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

using System.Linq;
using GoogleMobileAds.Api;

public static class CurrentGDPR
{
    private static bool _isGdprOn;
    private static string _purposeConsent, _vendorConsent, _vendorLi, _purposeLi, _partnerConsent;

    static CurrentGDPR()
    {
        InitData();
    }

    public static void InitData()
    {
        int gdprNum = 0;

        // [GoogleMobileAds 8.7.0]에서 ApplicationPreferences 추가됨
        // [참고] https://stackoverflow.com/questions/77838024/admob-gdpr-ump-issue-empty-iab-tcf-strings-on-android-after-user-consent
        // [참고] https://developers.google.com/admob/android/privacy/gdpr?hl=ko
        gdprNum = ApplicationPreferences.GetInt("IABTCF_gdprApplies");
        _purposeConsent = ApplicationPreferences.GetString("IABTCF_PurposeConsents");
        _vendorConsent = ApplicationPreferences.GetString("IABTCF_VendorConsents");
        _vendorLi = ApplicationPreferences.GetString("IABTCF_VendorLegitimateInterests");
        _purposeLi = ApplicationPreferences.GetString("IABTCF_PurposeLegitimateInterests");
        _partnerConsent = ApplicationPreferences.GetString("IABTCF_AddtlConsent");

        #region [GoogleMobileAds 8.6.0 이전] 버전
//#if UNITY_EDITOR // 에디터에서는 자바 호출이 에러나서 에외처리
//        gdprNum = 1;
//        _purposeConsent = "0000000000";
//        _vendorConsent = "0000000000";
//        _vendorLi = "";
//        _purposeLi = "";
//        _partnerConsent = "";
//#elif UNITY_ANDROID
//        AndroidJavaClass unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
//        AndroidJavaObject currentActivity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");
//        AndroidJavaClass preferenceManagerClass = new AndroidJavaClass("android.preference.PreferenceManager");
//        AndroidJavaObject sharedPreferences = 
//                 preferenceManagerClass.CallStatic<AndroidJavaObject>("getDefaultSharedPreferences", currentActivity);

//        gdprNum = sharedPreferences.Call<int>("getInt", "IABTCF_gdprApplies", 0);
//        _purposeConsent = sharedPreferences.Call<string>("getString", "IABTCF_PurposeConsents", "");
//        _vendorConsent = sharedPreferences.Call<string>("getString", "IABTCF_VendorConsents", "");
//        _vendorLi = sharedPreferences.Call<string>("getString", "IABTCF_VendorLegitimateInterests", "");
//        _purposeLi = sharedPreferences.Call<string>("getString", "IABTCF_PurposeLegitimateInterests", "");
//        _partnerConsent = sharedPreferences.Call<string>("getString", "IABTCF_AddtlConsent", "");
//#elif UNITY_IOS
//        gdprNum = PlayerPrefs.GetInt("IABTCF_gdprApplies", 0);
//        _purposeConsent = PlayerPrefs.GetString("IABTCF_PurposeConsents", "");
//        _vendorConsent = PlayerPrefs.GetString("IABTCF_VendorConsents", "");
//        _vendorLi = PlayerPrefs.GetString("IABTCF_VendorLegitimateInterests", "");
//        _purposeLi = PlayerPrefs.GetString("IABTCF_PurposeLegitimateInterests", "");
//        _partnerConsent = PlayerPrefs.GetString("IABTCF_AddtlConsent", "");
//#endif
        #endregion

        // 0 이면 아예 GDPR 대상이 아님. 1이어야 GDPR
        if (gdprNum == 1)
            _isGdprOn = true;
        else
            _isGdprOn = false;

        Debug.Log("GDPR을 띄우는가? " + _isGdprOn);
        Debug.Log("광고에 필요한 권한 동의: " + _purposeConsent);
        Debug.Log("광고에 필요한 적법관심(광고 개인화?) 동의: " + _vendorConsent);
        Debug.Log("구글에 동의처리가 되어있는가?: " + _vendorLi);
        Debug.Log("구글에 적법관심(광고 개인화?) 처리 여부: " + _purposeLi);
        Debug.Log("파트너 네트워크 여부: " + _partnerConsent);
    }

    // GDPR을 띄워야 할 유저인지(= 유럽 + 영국) 리턴
    public static bool IsGDPR()
    {
        return _isGdprOn;
    }

    // 광고가 보여지는지 여부 리턴
    public static bool CanAdShow()
    {
        int googleId = 755;
        bool hasGoogleVendorConsent = HasAttribute(_vendorConsent, googleId);
        bool hasGoogleVendorLi = HasAttribute(_vendorLi, googleId);

        // 광고 가능 - 비개인화 광고
        // return HasConsentFor(new List<int> { 1 }, _purposeConsent, hasGoogleVendorConsent)
        //        && HasConsentOrLegitimateInterestFor(new List<int> { 2, 7, 9, 10 }, 
        //            _purposeConsent, _purposeLi, hasGoogleVendorConsent, hasGoogleVendorLi);

        // 광고 가능 - 제한적인 광고 - 1에 대한 권한이 없어도 됨 ㅇㅇ
        return HasConsentOrLegitimateInterestFor(new List<int> { 2, 7, 9, 10 },
                   _purposeConsent, _purposeLi, hasGoogleVendorConsent, hasGoogleVendorLi);
    }

    // 개인화 광고가 보여지는지 여부 리턴
    public static bool CanShowPersonalizedAds()
    {
        int googleId = 755;
        bool hasGoogleVendorConsent = HasAttribute(_vendorConsent, googleId);
        bool hasGoogleVendorLi = HasAttribute(_vendorLi, googleId);

        return HasConsentFor(new List<int> { 1, 3, 4 }, _purposeConsent, hasGoogleVendorConsent)
               && HasConsentOrLegitimateInterestFor(new List<int> { 2, 7, 9, 10 },
                   _purposeConsent, _purposeLi, hasGoogleVendorConsent, hasGoogleVendorLi);
    }

    public static bool IsPartnerConsent(string partnerID) // 파트너 권한 있는지 확인
    {
        return _partnerConsent.Contains(partnerID);
    }

    // 이진 문자열의 "index" 위치에 "1"이 있는지 확인합니다(1 기반).
    private static bool HasAttribute(string input, int index)
    {
        return (index <= input.Length) && (input[index - 1] == '1');
    }

    // 목적 목록에 대한 동의가 주어졌는지 확인합니다.
    private static bool HasConsentFor(List<int> purposes, string purposeConsent, bool hasVendorConsent)
    {
        return purposes.All(p => HasAttribute(purposeConsent, p)) && hasVendorConsent;
    }

    // 목적 목록에 대한 공급자의 동의 또는 정당한 이익이 있는지 확인합니다.
    private static bool HasConsentOrLegitimateInterestFor(List<int> purposes, string purposeConsent, string purposeLI, bool hasVendorConsent, bool hasVendorLI)
    {
        return purposes.All(p =>
            (HasAttribute(purposeLI, p) && hasVendorLI) ||
            (HasAttribute(purposeConsent, p) && hasVendorConsent));
    }
}

 

 

[결론]

여기서 내가 필요했던

AppsFlyer에 넘기려던 정보는 CurrentGDPR.CanShowPersonalizedAds() 였다.

CurrentGDPR .IsGDPR()도 써도 될 것 같긴한데..

GoogleMobileAds에서 ConsentInformation.ConsentStatus를 사용중인데 뭘 써야될지는 좀 테스트 봐야 될 것 같다.

 

 

  [초기화 순서]

1. GoogleMobileAds -

    1.1. EEA 일 경우

        1.1.1. GDPR 동의 진행

            1.1.1.1. GDPR 동의 하면, (iOS만)IDFA 동의 진행(GDPR 동의 요청하면 IDFA도 필요하면 자동으로 진행 된다.)

            1.1.1.2. GDPR 동의 안하면, IDFA 동의 Skip(IDFA도 거부로 판단)

    1.2. EEA가 아닐 경우

        1.2.1. GDPR 동의 진행 안함

        1.2.2. (iOS만) IDFA 동의 진행

    1.3. GoogleMobileAds 초기화 진행

 

2. GDPR / (iOS만)IDFA 동의 진행 완료 후, AppsFlyer 초기화 진행

    2.1. AppsFlyerConsent.ForGDPRUser() || AppsFlyerConsent.ForNonGDPRUser() 셋팅

    2.2. AppsFlyer.startSDK() 실행

 

 

[참고] https://developers.google.com/admob/unity/privacy/gdpr?hl=ko

[참고] https://stackoverflow.com/questions/69307205/mandatory-consent-for-admob-user-messaging-platform

[참고] https://stackoverflow.com/questions/77838024/admob-gdpr-ump-issue-empty-iab-tcf-strings-on-android-after-user-consent

[참고] https://stackoverflow.com/questions/69307205/mandatory-consent-for-admob-user-messaging-platform

[참고] https://gall.dcinside.com/mgallery/board/view/?id=game_dev&no=150987

[참고] https://developers.google.com/admob/unity/privacy/gdpr?hl=ko

[참고] https://ko.dev.appsflyer.com/hc/docs/basicintegration

 

 

 

 

반응형
Posted by blueasa
, |
1

 

얘들아 안녕, 다들 개발 잘 하고 있어?

 

 

저번 달에 GDPR 구현하다가 막혀서 여기다 글을 올렸었는데, 다행히 이젠 해결한 것 같아.

 

그 당시 해결하면 팁 남겨달라던 친구의 댓글이 기억나서 다시 돌아왔어.

 

 

사실 GDPR은 나온지가 진짜 오래 되었어.

 

심지어 구글 애드몹 기준으로도 ‘24년 1월 16일까지 대응 필수!’라며 경고를 엄청 띄워왔었기에 대부분 대응을 완료 했을 거라고 생각해.

 

그래도 이제 개발하는 친구들은 모를 수도 있을테니 도움이 되었으면 하는 마음에 남겨봐.

 

 

그럼 시작해보자.

 

 

 

 

GDPR이 뭐임?

 

정확한 명칭은 유럽 개인정보 보호법이라는데, 간단히 이야기하면

 

“유럽 유저들의 개인 정보를 가져다 쓰려면 직접 허락을 받아라’”

 

라고 할 수 있어. 애플의 ATT와 비슷한데, 조건이 안 맞으면 광고를 아예 못 튼다는 부분에서 조금 더 빡센 느낌이야.

 

더 상세한 내용이 궁금한 친구들은 구글에 GDPR로 검색하면 자료가 쏟아지니까 확인해보자.

 

사실 인디 게임 개발자인 우리가 알아야 될 것은 하나인 것 같아.

 

“유럽(+ 영국)에서 광고로 돈 벌려면 GDPR 동의 팝업을 추가해야 함.”

 

 

 

 

 

GDPR 대응 안하면 어떻게 되는데?

 

중요한 건 GDPR의 대상이 그 이름처럼 유럽(+영국) 한정이라는거야.

 

즉, 아직 글로벌 서비스를 계획하고 있지 않다면 신경 쓸 필요가 없어.

 

하지만 서비스의 대상에 유럽(+영국)가 포함된다면, GDPR 동의를 받지 않은 유저들에겐 광고를 아예 띄울 수 없어.

 

다만 이미 서비스 중인 게임 기준, 따로 GDPR을 구현하지 않더라도 어떻게든 애드몹이 온몸 비틀기로 GDPR 동의를 띄우는 것 같아.

 

다만 제대로 된 팝업은 아닌 것으로 보이고, 그래서인지 동의율이 낮아.

 

거기다 애드몹이 이런 식의 땜빵을 꾸준히 해준다는 보장도 없으니, 아직 대응을 안했거나 신규 개발 중이라면 GDPR 처리를 해 두는걸 권장해.

 

 

 

 

 

그렇다면 어떻게 대응하는가?

 

앞에서 말했듯이 애드몹에서 최근에 필수로 변경해서 그렇지, GDPR 자체는 몇 년도 전에 있었어.

 

때문에 GDPR을 처리할 수 있는 방법은 다양해.

 

애드몹처럼 광고 플랫폼이 제공하는 기능을 사용해도 되고, 아예 GDPR을 별도로 처리해주는 서비스도 있다고 들었어.

 

근데 난 애드몹으로 처리했으니, 애드몹 기준으로 설명할게.

 

애드몹에서 GDPR을 처리하려면, 크게 2가지가 필요해.

 

바로 팝업과 구현이야.

 

 

 

 

 

 

팝업 추가하기

 

 

2

먼저 애드몹의 ‘개인 정보 보호 및 메시지’ 메뉴로 들어가서, 유럽 규정의 ‘관리’로 들어가.

 

 

3

 

그럼 이렇게 메시지 만들기를 선택할 수 있고

 

 

4

 

이후에 나오는 페이지에서 GDPR 페이지 설정을 마무리하면 돼.

 

 

여기서 신경 써야 할 것은 3가지인 것 같아.

 

1, 2 - 둘 다 유저에게 미동의 버튼을 얼마나 적극적으로 보여주느냐를 결정하는 기능이야.

개발자 입장에선 동의율이 높은 게 좋으니 둘 다 사용안함으로 두는 게 유리할 거야.

 

3 - 해당 팝업을 몇 가지 언어로 지원하는지를 결정하는 부분이야.

기본은 영어로 되어있고, 다양한 언어를 지원하길래 나는 31개 추가언어를 모두 활성화했어.

 

여기까지 세팅하고 ‘게시’ 버튼 누르면 팝업에 대한 세팅은 끝이야.

 

 

 

 

 

팝업 구현하기

 

저것만 추가하고 끝나면 참 좋은데... 안타깝게도 코드 딴에서 직접 저 팝업을 호출해줘야 하더라.

 

애드몹에서 직접 설명하고 있고, 코드가 복잡하지 않으니 직접 확인해보면 될 거야.

 

https://developers.google.com/admob/unity/privacy?hl=ko

 

하면서 내가 겪었던 문제를 몇 가지 공유하면 다음과 같아.

 

- 싱글 스레드 에러가 발생하면 아래 코드를 추가해 줘야 함.

  MobileAds.RaiseAdEventsOnUnityMainThread = true;

 

- 테스트를 위해 핸드폰의 Hashed ID가 필요한데, 테스트 빌드를 로그캣에 물려서 돌려보면 로그에 찍힘.

 

이렇게 코드까지 추가해주면 기본적인 작업은 끝이야.

 

 

 

 

 

추가 처리 (선택)

 

앞선 2개만 처리하면 돌리는 것은 문제가 없어. 그러니 대부분의 경우엔 이 정도에서 구현을 마쳐도 괜찮을거야.

 

하지만 내 경우는 상세한 정보를 필요했는데, 이런 것들이야.

 

- 이 유저가 GDPR의 대상인지 아닌지

- 지금 애드몹이 광고를 안주는게 유저가 GDPR 동의를 안 해서 그런 건지, 그냥 광고 슬롯이 빈 건지

- 개인화 광고 여부

 

특히 ‘주모 키우기’에선 광고 슬롯이 비어 있는 게 유저 잘못은 아니라고 생각해서 리워드 보상을 주고 있었단 말야.

 

하지만 GDPR에 비동의한 유저도 광고 슬롯은 똑같이 비어있는 것으로 확인되었고, 때문에 GDPR의 동의 상태 확인이 무척 중요해졌지.

 

저번에 문의 글을 남긴 이유도 이 부분의 방법을 찾지 못해서 그런거였어.

 

하지만 열심히 뒤지다보니 다 방법이 있긴 하더라.

 

여기서부터는 링크로 대체할게.

 

 

애드몹에서 GDPR 동의 수준 확인하는 법

https://stackoverflow.com/questions/69307205/mandatory-consent-for-admob-user-messaging-platform

 

유니티에서 자바 클래스 호출해서 확인하는 법

https://groups.google.com/g/google-admob-ads-sdk/c/uIQkJX6_XtM/m/KIFmbfXVAQAJ

 

애드몹의 파트너 플랫폼 별 ID 확인 (앱로빈 - 1301, 유니티애즈 - 3234)

https://support.google.com/admob/answer/9681920?hl=en // 이 페이지의 Where will the Google ATPs be published? 메뉴에서 받음

 

 

여기 글들을 잘 읽어보면 대응이 가능하긴 한데... 솔직히 나도 엄청 헤맸다보니 사람에 따라선 부족할 수 있다 싶어.

 

그러니 내가 구현한 코드도 함께 남겨둘게. 이해가 어려운 친구들은 참고해 봐.

    private void Start()

    {       
        MobileAds.RaiseAdEventsOnUnityMainThread = true; // 애드몹 관련 처리는 메인 스레드에서만 처리하도록 처리

        #if !UNITY_EDITOR // 에디터에선 GDPR 동의 홀드
            Debug.Log("GDPR 동의 프로세스 스타트");
            #if DEBUGBUILD // 테스트 빌드에서만 테스트모드 활성화
                Debug.Log("테스트 버전 - 초기화 후 체크 개시!");
                ConsentInformation.Reset(); // 테스트를 위해 기존 GDPR 정보 초기화
                var debugSettings = new ConsentDebugSettings
                {
                    DebugGeography = DebugGeography.EEA, // 일시적으로 유럽인척
                    #if UNITY_IOS
                        TestDeviceHashedIds = new List<string> {"여기에 아이폰 해시 ID"}
                    #else
                        TestDeviceHashedIds = new List<string> {"여기에 안드로이드 해시 ID"}
                    #endif                
                };
                ConsentRequestParameters request = new ConsentRequestParameters {ConsentDebugSettings = debugSettings};
                ConsentInformation.Update(request, OnConsentInfoUpdated);
            #else // 릴리즈 & 디스트리뷰트에선 GDPR 테스트 모드 끄기
                Debug.Log("릴리즈 버전 - 체크 개시!");
                ConsentRequestParameters request = new ConsentRequestParameters();
                ConsentInformation.Update(request, OnConsentInfoUpdated);
            #endif
        #else       
            // 구글 애즈 초기화
            Debug.Log("애드몹 초기화 시도!");
            MobileAds.Initialize(initStatus =>
            {
                StartAdSet();
            });
        #endif
    }

    void OnConsentInfoUpdated(FormError consentError)
    {
        Debug.Log("GDPR 동의 상태 콜백 확인");
        if (consentError != null)
        {
            Debug.Log("동의 상태 확인 실패: " + consentError);
            return;
        }
        Debug.Log("GDPR 동의 상태 확인 완료!");
        
        ConsentForm.LoadAndShowConsentFormIfRequired((FormError formError) =>
        {
            Debug.Log("양식 로드 시도");
            if (formError != null)
            {
                Debug.Log("동의 획득 실패: " + consentError);
                return;
            }
          
            if (CurrentGdpr.IsGDPR()) // GDPR을 검사하는 국가에서만 체크
            {
                isNoAd = !CurrentGdpr.CanAdShow();
                if (CurrentGdpr.IsPartnerConsent("1301")) // 앱로빈 확인
                { 
                    AppLovin.SetHasUserConsent(true);
                    Debug.Log("앱로빈 GDPR 켜짐");
                }
                
                if (CurrentGdpr.IsPartnerConsent("3234")) // 유니티애즈 확인
                { 
                    UnityAds.SetConsentMetaData("gdpr.consent", true);
                    Debug.Log("유니티애즈 GDPR 켜짐");
                }
            }
            else
                isNoAd = false;

            Debug.Log("GDPR 적용 여부: " + CurrentGdpr.IsGDPR());
            Debug.Log("동의 성공. 현재 광고 재생 가능? " + !isNoAd);
            Debug.Log("개인화된 광고 가능? " + CurrentGdpr.CanShowPersonalizedAds());
            
            if (ConsentInformation.CanRequestAds())
            {
                // 구글 애즈 초기화
                Debug.Log("애드몹 초기화 시도!");
                MobileAds.Initialize(initStatus =>
                {
                    StartAdSet();
                });
            }
            else
                Debug.Log("광고 요청 불가 상태...");
        });
    }

 

여기까지가 GDPR 세팅 및 팝업 호출 & 애드몹 초기화고

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

using System.Linq;

public static class CurrentGdpr
{
    private static bool _isGdprOn;
    private static string _purposeConsent, _vendorConsent, _vendorLi, _purposeLi, _partnerConsent;
    
    static CurrentGdpr()
    {
        SetData();
    }
    
    public static void SetData()
    {
        int gdprNum;

#if UNITY_EDITOR // 에디터에서는 자바 호출이 에러나서 에외처리
        gdprNum = 1;
        _purposeConsent = "0000000000";
        _vendorConsent = "0000000000";
        _vendorLi = "";
        _purposeLi = "";
        _partnerConsent = "";
#elif UNITY_ANDROID
        AndroidJavaClass unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
        AndroidJavaObject currentActivity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");
        AndroidJavaClass preferenceManagerClass = new AndroidJavaClass("android.preference.PreferenceManager");
        AndroidJavaObject sharedPreferences = 
                 preferenceManagerClass.CallStatic<AndroidJavaObject>("getDefaultSharedPreferences", currentActivity);

        gdprNum = sharedPreferences.Call<int>("getInt", "IABTCF_gdprApplies", 0);
        _purposeConsent = sharedPreferences.Call<string>("getString", "IABTCF_PurposeConsents", "");
        _vendorConsent = sharedPreferences.Call<string>("getString", "IABTCF_VendorConsents", "");
        _vendorLi = sharedPreferences.Call<string>("getString", "IABTCF_VendorLegitimateInterests", "");
        _purposeLi = sharedPreferences.Call<string>("getString", "IABTCF_PurposeLegitimateInterests", "");
        _partnerConsent = sharedPreferences.Call<string>("getString", "IABTCF_AddtlConsent", "");
#elif UNITY_IOS
        gdprNum = PlayerPrefs.GetInt("IABTCF_gdprApplies", 0);
        _purposeConsent = PlayerPrefs.GetString("IABTCF_PurposeConsents", "");
        _vendorConsent = PlayerPrefs.GetString("IABTCF_VendorConsents", "");
        _vendorLi = PlayerPrefs.GetString("IABTCF_VendorLegitimateInterests", "");
        _purposeLi = PlayerPrefs.GetString("IABTCF_PurposeLegitimateInterests", "");
        _partnerConsent = PlayerPrefs.GetString("IABTCF_AddtlConsent", "");
#endif
        // 0 이면 아예 GDPR 대상이 아님. 1이어야 GDPR
        if (gdprNum == 1)
            _isGdprOn = true;
        else  
            _isGdprOn = false;
        
        Debug.Log("GDPR을 띄우는가? " + _isGdprOn);
        Debug.Log("광고에 필요한 권한 동의: " + _purposeConsent);
        Debug.Log("광고에 필요한 적법관심(?) 동의: " + _vendorConsent);
        Debug.Log("구글에 동의처리가 되어있는가?: " + _vendorLi);
        Debug.Log("구글에 적법관심(?) 처리 여부: " + _purposeLi);
        Debug.Log("파트너 네트워크 여부: " + _partnerConsent);
    }

    // GDPR을 띄워야 할 유저인지(= 유럽 + 영국) 리턴
    public static bool IsGDPR()
    {
        return _isGdprOn;
    }

    // 광고가 보여지는지 여부 리턴
    public static bool CanAdShow()
    {
        int googleId = 755;
        bool hasGoogleVendorConsent = HasAttribute(_vendorConsent, googleId);
        bool hasGoogleVendorLi = HasAttribute(_vendorLi, googleId);

        // 광고 가능 - 비개인화 광고
        // return HasConsentFor(new List<int> { 1 }, _purposeConsent, hasGoogleVendorConsent)
        //        && HasConsentOrLegitimateInterestFor(new List<int> { 2, 7, 9, 10 }, 
        //            _purposeConsent, _purposeLi, hasGoogleVendorConsent, hasGoogleVendorLi);
        
        // 광고 가능 - 제한적인 광고 - 1에 대한 권한이 없어도 됨 ㅇㅇ
        return HasConsentOrLegitimateInterestFor(new List<int> { 2, 7, 9, 10 }, 
                   _purposeConsent, _purposeLi, hasGoogleVendorConsent, hasGoogleVendorLi);
    }

    // 개인화 광고가 보여지는지 여부 리턴
    public static bool CanShowPersonalizedAds()
    {
        int googleId = 755;
        bool hasGoogleVendorConsent = HasAttribute(_vendorConsent, googleId);
        bool hasGoogleVendorLi = HasAttribute(_vendorLi, googleId);

        return HasConsentFor(new List<int> { 1, 3, 4 }, _purposeConsent, hasGoogleVendorConsent)
               && HasConsentOrLegitimateInterestFor(new List<int> { 2, 7, 9, 10 }, 
                   _purposeConsent, _purposeLi, hasGoogleVendorConsent, hasGoogleVendorLi);
    }

    public static bool IsPartnerConsent(string partnerID) // 파트너 권한 있는지 확인
    {
        return _partnerConsent.Contains(partnerID);
    }
    
    // 이진 문자열의 "index" 위치에 "1"이 있는지 확인합니다(1 기반).
    private static bool HasAttribute(string input, int index)
    {
        return input.Length >= index && input[index - 1] == '1';
    }
    
    // 목적 목록에 대한 동의가 주어졌는지 확인합니다.
    private static bool HasConsentFor(List<int> purposes, string purposeConsent, bool hasVendorConsent)
    {
        return purposes.All(p => HasAttribute(purposeConsent, p)) && hasVendorConsent;
    }
    
    // 목적 목록에 대한 공급자의 동의 또는 정당한 이익이 있는지 확인합니다.
    private static bool HasConsentOrLegitimateInterestFor(List<int> purposes, string purposeConsent, string purposeLI, bool hasVendorConsent, bool hasVendorLI)
    {
        return purposes.All(p =>
            (HasAttribute(purposeLI, p) && hasVendorLI) ||
            (HasAttribute(purposeConsent, p) && hasVendorConsent));
    }
}

이건 GDPR에 대한 세부 속성을 확인하는 코드야.

 

위의 링크에 있는 코드들을 C#으로 변경하고, 최신 상황에 맞춰 수정한거야.

 

 

 

 

 

 

 

 

자, 대충 여기까지야.

 

나 또한 구현에 시간이 걸리다보니 애드몹이 말한 제한시간을 넘겨서 GDPR을 추가하게 되었어.

 

그러다보니 본의 아니게 애드몹 기본 제공 GDPR을 몇 일간 사용하게 되었었는데, 차이는 다음과 같아.

 

 

5
6

 

위쪽이 애드몹에서 제공하는 기본 구현일 때, 아래쪽이 지금 상황이야. 1주일 남짓인데 그 사이에 레이아웃이 바뀌었네. 흠?;

 

데이터가 적어 객관성은 떨어지지만, 일단 구현을 처리한 쪽이 동의율이 높긴 한 것 같아.

 

위에서 따로 이야기는 안했지만, GDPR 동의를 끈 유저들이 광고 시청을 시도할 때는 '옵션에서 GDPR을 켜!' 라고 안내도 하고 있어.

 

 

 

 

 

어때, 조금 도움이 되었을까?

 

처음 게임을 출시할 때 GDPR 같은 것은 신경 안써도 되었었는데, 어째 점점 챙겨야 될 게 많아지는 느낌이네.

 

1주일 넘게 서비스하면서 문제를 발견하진 못했지만, 맨 땅에 헤딩하면서 찾은 정보다 보니 틀린 내용도 있을 수 있어.

 

혹시 잘 아는 친구들은 수정사항을 댓글로 남겨주면 맞춰서 반영할게.

 

자 그럼 다들 개발 열심히 해!

 

 

[출처] https://gall.dcinside.com/mgallery/board/view/?id=game_dev&no=150987

 

GDPR에 대해 알아보자 (Feat 애드몹) - 인디 게임 개발 마이너 갤러리

얘들아 안녕, 다들 개발 잘 하고 있어?저번 달에 GDPR 구현하다가 막혀서 여기다 글을 올렸었는데, 다행히 이젠 해결한 것 같아.그 당시 해결하면 팁 남겨달라던 친구의 댓글이 기억나서 다시 돌

gall.dcinside.com

 

반응형
Posted by blueasa
, |

Unity 2021.3.35f1

GoogleMobileAds 8.7.0

----

 

 

GDPR 동의 로직 추가하고 iOS 검수 넣었더니 리젝 됐다.

사유는 대충 정리하면,

GDPR 팝업에서 '거부'를 했는데, 같은 이슈인 'IDFA(AppTrackingTransparency)' 동의 여부를 다시 묻는 건 문제다.

라는 말이었습니다.

 

이전엔 GoogleMobileAds에서 제대로 처리안해줘서 같은 이슈 (아래 링크 참고) 가 있었던 것 같은데, 현재 최신 버전에서는 수정된걸로 보인다.

[참고] https://groups.google.com/g/google-admob-ads-sdk/c/huUa7eyMTEE

 

URGENT: Apple App Rejected - UMP SDK Using both GDPR & IDFA/ATT Causing Rejection

Hi Justin, thank you for the update and for your work to get this resolved! Unfortunately, the "short term recommendation" provided would be a significant ad revenue impact, and so it is not viable. We do need to call requestConsentInfoUpdate to get the I

groups.google.com

 

아무튼,

나의 경우는 GDPR은 GoogleMobileAds를 사용하고, IDFA 관련은 다른 SDK에서 처리하고 있어서 유기적으로 제어되지 않고 있었다.

 

GoogleMobileAds-UMP를 확인해보니,

IDFA(App Tracking Transparency) 관련 처리도 GoogleMobileAds에서 같이해주면 GDPR과 IDFA(ATT) 동의를 알아서 유기적으로 처리해준다.

 

GDPR 로직을 추가하고, IDFA 관련 Admob 설정을 추가(소스 로직 추가는 없음)하고, 테스트 해보니 아래와 같은 형태로 진행된다.

(로직상으로는 GDPR 관련 체크 로직만 있다. GDPR 체크 후에 필요하면 IDFA 로직을 알아서 타는 것 같다.)

 

GDPR 관련 설정은 아래 링크를 참고하자.

[링크] [GoogleMobileAds] Unity-Google-UMP-Check(GDPR)

 

[GoogleMobileAds] Unity-Google-UMP-Check(GDPR)

2024년 1월 16일까지(참조:https://support.google.com/admob/answer/14189727?hl=ko) Admob을 사용하려면 유저에게 GDPR 동의를 받으라고 하는 것 같다. 그래서 Google에서 관련 SDK를 내놓은게 UMP(User Messaging Platform) SDK

blueasa.tistory.com

 

[GDPR/IDFA 활성화 시, 진행 Flow]

1) EEA(European Economic Area, 유럽 경제 지역) 경우

    1-1) GDPR 팝업 Open

            1-1-1) GDPR 동의 시 -> ATT 동의 팝업 Open

            1-1-2) GDPR 비동의 시 -> ATT 동의 팝업 Skip(뜨지 않음)

 

2) EEA(European Economic Area, 유럽 경제 지역)가 아닌 경우

    2-1) IDFA 메시지 (안내) 팝업 Open

    2-2) ATT(App Tracking Transparency) 동의 팝업 Open

 

애플 검수 과정에서 요구하는 사항인 GDPR을 거부했을 때, IDFA 동의 팝업이 뜨지 않아야 된다는 조건에 만족하고 있다.

 

[결론]

GoogleMobileAds-UMP로 GDPR과 IDFA를 처리하면 심플하게 유기적으로 처리 가능하다.

적극 활용하자.

 

 

[참조] https://docs.adxcorp.kr/appendix/ump-user-messaging-platform#2.-idfa

 

UMP (User Messaging Platform) - ADX Library

IDFA 메시지 작성은 선택사항이지만, GDPR 메시지 사용 설정을 할 경우, IDFA 메시지 작성도 같이 작성하십시오. 애드몹 UMP의 GDPR 동의 화면이 보이는 상태에서, 프로그래밍 방식으로 수동으로 ATT (AP

docs.adxcorp.kr

[참조] https://docs.adxcorp.kr/ios/supporting-ios-14/app-tracking-transparency

 

App Tracking Transparency - ADX Library

애드몹 UMP (User Messaging Platform)의 IDFA 메시지 기능 활성화 시, 특별한 프로그래밍 코딩을 하지 않아도, UMP 내부에서 자동으로 ATT 동의 알림 요청 기능을 수행하므로, 이 단계(Step4)와 다음 단계 (Step

docs.adxcorp.kr

 

반응형
Posted by blueasa
, |

[Google Play Store 경고 내용]

com.google.android.recaptcha:recaptcha:18.1.2
이 SDK 버전에는 SDK 개발자의 메모가 포함되어 있습니다. SDK 개발자가 신고한 내용은 다음과 같습니다.
A critical security vulnerability was discovered in reCAPTCHA Enterprise for Mobile. The vulnerability has been patched in the latest SDK release. Customers will need to update their Android application with the reCAPTCHA Enterprise for Mobile SDK, version 18.4.0 or above. We strongly recommend you update to the latest version as soon as possible.

 

[수정]

Firebase 11.7.0(Firebase Android BoM version 32.7.1)에 수정됐다고 한다.

Firebase 11.7.0으로 버전업 하자.

 

 

[참조] https://velog.io/@hodu_angel/Firebase-com.google.android.recaptcharecaptcha18.1.2

 

velog

 

velog.io

 

[참조] https://github.com/firebase/firebase-android-sdk/issues/5638

 

reCAPTCHA Enterprise update · Issue #5638 · firebase/firebase-android-sdk

A critical security vulnerability was discovered in reCAPTCHA Enterprise for Mobile. The vulnerability has been patched in the latest SDK release. Customers will need to update their Android applic...

github.com

 

반응형
Posted by blueasa
, |

Unity 2021.3.34f1

----

 

Unity 2021.3.34f1에서 Android 빌드 테스트를 하다보니 아래와 같이 2가지 이슈가 있었다.

 

1) Android Target API Level 31 이후가 뜨지 않음.

2) Android 빌드 시, 빌드는 잘되지만 켜자마자 Runtime Crash가 발생함.

----

 

[1) 이슈]

1)의 경우는 Unity 설치 Path에 빈 칸( )이 있어서 발생하는 문제라고 한다.

 

[결론]

Unity Hub의 설치 폴더를 빈 칸( )이 없는 Path로 변경하고(Unity Hub 완전 종료 후 재실행 필요) 유니티를 다시 설치하자.

 

 

[2) 이슈]

유니티 업데이트 하고 1)의 이슈 해결하고 Android 빌드했더니, 아래와 같은 Crash 로그가 뜬다.

(iOS는 정상적으로 빌드 됨)

 

[Crash Log] signal 6 (SIGABRT), code -1 (SI_QUEUE)

 

검색해보니 아래 [참조]와 같은 내용과 해결책이 적혀 있는데,

따라해봐도 계속 Crash가 나는걸 봐선 이번 이슈와는 관련이 없는 것 같다.

(결국 Crash Log로 검색해서는 현재 상황에 맞는 답을 찾지 못했다)

 

[참조] https://stackoverflow.com/questions/76222872/unity-android-receiving-signal-crashes

 

Unity Android receiving signal crashes

Backstory I built my unity app to android x64 using IL2CPP which has worked fine in the past but after a lot of changes I'm now getting crashes somewhat spontaneously. Signals received: signal 6 (

stackoverflow.com

 

그래서 좀 더 고민하다보니 현재 프로젝트 2개를 관리중인데 Unity 2021.3.34f1에서 하나는 Runtime Crash가 나고, 하나는 Crash가 나지 않아서 비교해 보다보니 다른점이 있었다.

Crash가 나는 쪽은 대용량(150MB 이상) aab 파일 생성을 위해 Play Asset Delivery 1.7.0이 들어있었고,

Crash 가 나지 않는 쪽은 없었다.

 

혹시나하고 Play Asset Delivery를 지우고 빌드해보니 정상적으로 빌드되고 잘 실행된다.

그래서 다시 한 번 Play Asset Delivery를 현재 기준 최신 버전인 1.8.2(2024-02-16 현재 기준 최신)를 넣고 빌드해보니 역시나 잘 실행된다.

Unity 2021.3.34f1부터 Google 라이브러리 중 뭔가 바꼈다고 하는 것 같은데,

정황상 바뀐 Google 관련 라이브러리가 Play Asset Delivery 1.7.0 버전과 호환이 안되는 것 같다.

 

이전에는 Unity 2021의 gradle 버전이 기본 4.0.1이어서 Play Asset Delivery 1.7.0을 써야 됐는데,

GoogleMobileAds(Admob) 8.6.0 버전부터 gradle 4.2.0을 강제해서 올리다보니

Play Asset Delivery 1.8.x 버전대를 쓸 수 있는 상황이 왔다.

 

[결론]

Unity 2021.3.34f1 이상에서 제목과 같은 Runtime Crash가 나는데 Play Asset Delivery 1.7.0을 사용하고 있다면,

gradle 4.2.0으로 올리고, Play Asset Delivery 1.8.2(2024-02-16 현재 기준 최신)로 교체한 후 빌드해 보자.

 

[정리]

Unity 2021.3.33f1 이하 : Play Asset Delivery 1.7.0 사용

Unity 2021.3.34f1 이상 : Play Asset Delivery 1.8.2 사용

 

 

[참조] https://developers.google.com/unity/archive?hl=ko#play_asset_delivery

 

Unity용 Google 패키지 다운로드  |  Google for Developers

 

developers.google.com

반응형
Posted by blueasa
, |

Unity 2021.3.34f1

----

 

Unity 2021.3.33f1에선 잘되던게 Unity 2021.3.34f1을 설치하고 아래와 같은 에러가 뜨면서 Android Target API Level 31 이상이 뜨지 않는다.

해당 증상은 Unity 2021.3.35f1에서도 해결되지 않았다.(고칠 생각이 없는건가?)

 

[Error Log]

CommandInvokationFailure: Failed to update Android SDK package list.
C:\Program Files\Unity\Hub\Editor\2021.3.34f1\Editor\Data\PlaybackEngines\AndroidPlayer\SDK\cmdline-tools\2.1\bin\sdkmanager.bat --list

 

관련 이슈 및 해결방법은 아래 참조 링크에서 볼 수 있다.

 

관련 문제는 결국 기본 설정 폴더인 'C:\Program Files' Path의 사이에 있는 빈 칸( ) 때문이란다.

유니티 관계자는 구글 탓을 하고 있는데,

Unity Hub에서 에디터 기본 설치 폴더를 'C:\Program Files\Unity\Hub\Editor'로 잡고 있으면서 남 탓 하는것도 웃기고..

Unity 2021.3.34f1을 기본 설치폴더에 설치하고 실행만 해봤어도 아는 버그를.. 결국 테스트도 안한다고 자백하는 꼴 아닌가..

 

아무튼 해결 방법 2가지를 제시 하는데,

나중을 생각해서라도 심플하게 아래와 같이

Unity Editor 기본 설치 폴더를 'C:\Program Files\Unity\Hub\Editor'에서 'C:\Unity\Hub\Editor'로 옮기자.

 

 

[참조]

https://forum.unity.com/threads/commandinvokationfailure-failed-to-update-android-sdk-package-list.1535458/

 

Bug - CommandInvokationFailure: Failed to update Android SDK package list.

Just updated to 2021.3.34f1. Using all default Unity supplied SDKs. Unable to list target platforms. Please make sure the android sdk path is correct....

forum.unity.com

 

반응형
Posted by blueasa
, |