대형 화면 크기 조절 지원

휴대전화에서 다양한 대형 화면 폼 팩터로 확장하면 게임에서 창 관리를 처리하는 방식을 고려해야 합니다. Googlebook, ChromeOS, PC용 Google Play 게임즈에서 게임은 기본 데스크톱 인터페이스를 통해 창 모드로 실행될 수 있습니다. Android 12L (API 수준 32) 이상을 실행하고 화면 너비가 600dp보다 큰 새 Android 태블릿과 폴더블 기기에서 게임은 다른 애플리케이션과 함께 화면 분할 모드로 나란히 실행되고, 크기가 조절되며, 폴더블 기기의 내부 디스플레이와 외부 디스플레이 간에 이동될 수도 있습니다. 이로 인해 창 크기와 일부 기기에서는 방향이 변경됩니다.

Unity 게임의 크기 조절 가능 여부

기본 대형 화면 구성

게임에서 크기 조정을 처리할 수 있는지 선언합니다.

<android:resizeableActivity="true" or "false" />

크기 조정을 지원할 수 없는 경우 게임 매니페스트에서 지원되는 최소 및 최대 가로세로 비율을 명시적으로 정의해야 합니다.

<!-- Render full screen between 3:2 and 21:9 aspect ratio -->
<!-- Let the platform letterbox otherwise -->
<activity android:minAspectRatio="1.5">
<activity android:maxAspectRatio="2.33">

PC용 Google Play 게임즈

PC용 Google Play 게임즈의 경우 플랫폼에서 지정된 가로세로 비율을 준수하면서 창 크기 조절을 처리합니다. 창 크기가 최적의 크기로 자동 잠금됩니다. 기본 방향이 가로 모드인 경우 16:9 가로세로 비율을, 게임이 세로 모드인 경우 9:16 가로세로 비율을 지원해야 합니다. 최상의 환경을 위해 가로 모드 게임에서 21:9, 16:10, 3:2 가로세로 비율을 명시적으로 지원하세요. 여기서는 창 크기 조절이 필요하지 않지만 다른 폼 팩터 호환성을 위해 있는 것이 좋습니다.

자세한 내용과 권장사항은 PC용 Google Play 게임즈 그래픽 구성을 참고하세요.

큰 화면

대형 화면 태블릿, 폴더블, Googlebook, ChromeOS, 연결된 디스플레이에서 게임의 표시 영역을 최대화하려면 전체 화면 몰입형 모드를 지원하고 decorView, 시스템 UI 표시 상태 또는 WindowInsetsCompat API에서 플래그를 설정하여 시스템 표시줄을 숨깁니다. 또한 회전 및 크기 조절 구성 이벤트를 적절하게 처리하거나 이러한 이벤트가 발생하지 않도록 해야 합니다.

대형 화면 Android 기기에서는 게임이 아직 처리하지 않는 구성으로 실행될 수 있습니다. 게임에서 일부 창 크기 및 방향 구성을 지원하지 않는 경우 플랫폼에서 호환성 모드로 게임에 레터박스를 적용하고, 필요한 경우 지원되지 않는 구성으로 변경하기 전에 플레이어에게 메시지를 표시합니다.

그림 1. 구성 호환성 대화상자

일부 기기에서는 플레이어가 지원되지 않는 구성으로 이동하면 게임을 다시 로드하고 새 창 레이아웃에 가장 적합하도록 활동을 다시 만들라는 메시지가 표시될 수 있으며, 이로 인해 플레이 환경이 중단됩니다. 다양한 멀티 윈도우 모드 구성 (2/3, 1/2, 1/3 윈도우 크기)에서 게임을 테스트하고 게임플레이나 UI 요소가 잘리거나 액세스할 수 없게 되지 않는지 확인합니다. 또한 폴더블 기기에서 내부 화면과 외부 화면 간에 이동할 때 게임이 폴더블 연속성에 어떻게 반응하는지 테스트합니다. 문제가 표시되면 이러한 구성 이벤트를 명시적으로 처리하고 고급 대형 화면 크기 조절 지원을 추가하세요.

고급 대형 화면 크기 조절

그림 2. 데스크톱과 폴더블의 탁자 모드에서 서로 다른 UI

호환성 모드를 종료하고 활동 재생성을 방지하려면 다음을 실행하세요.

  1. 기본 활동을 크기 조절 가능으로 선언합니다.

    <android:resizeableActivity="true" />
    
  2. 게임 매니페스트의 <activity> 요소의 android:configChanges 속성에서 'orientation', 'screenSize', 'smallestScreenSize', 'screenLayout', 'density'를 명시적으로 지원한다고 선언하여 모든 대형 화면 구성 이벤트를 수신합니다.

    <android:configChanges="screenSize | smallestScreenSize | screenLayout | orientation | keyboard |
                            keyboardHidden | density" />
    
  3. onConfigurationChanged()를 재정의하고 현재 방향, 창 크기, 너비, 높이를 포함한 구성 이벤트를 처리합니다.

    Kotlin

    override fun onConfigurationChanged(newConfig: Configuration) {
       super.onConfigurationChanged(newConfig)
       val density: Float = resources.displayMetrics.density
       val newScreenWidthPixels =
    (newConfig.screenWidthDp * density).toInt()
       val newScreenHeightPixels =
    (newConfig.screenHeightDp * density).toInt()
    
       // Configuration.ORIENTATION_PORTRAIT or ORIENTATION_LANDSCAPE
       val newScreenOrientation: Int = newConfig.orientation
    
       // ROTATION_0, ROTATION_90, ROTATION_180, or ROTATION_270
       val newScreenRotation: Int =
    windowManager.defaultDisplay.rotation
    }

    자바

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
       super.onConfigurationChanged(newConfig);
       float density = getResources().getDisplayMetrics().density;
       int newScreenWidthPixels = (int) (newConfig.screenWidthDp * density);
       int newScreenHeightPixels = (int) (newConfig.screenHeightDp * density);
    
       // Configuration.ORIENTATION_PORTRAIT or ORIENTATION_LANDSCAPE
       int newScreenOrientation = newConfig.orientation;
    
       // ROTATION_0, ROTATION_90, ROTATION_180, or ROTATION_270
       int newScreenRotation = getWindowManager().getDefaultDisplay()
               .getRotation();
    }

WindowManager를 쿼리하여 현재 기기 회전을 확인할 수도 있습니다. 이 메타데이터를 사용하여 새 창 크기를 확인하고 전체 창 크기로 렌더링합니다. 가로세로 비율 차이로 인해 모든 경우에 작동하지 않을 수 있으므로 게임 UI를 새 창 크기에 고정하고 핵심 게임플레이 콘텐츠를 레터박스 처리하는 것이 좋습니다. 두 접근 방식 중 하나를 방해하는 기술적 또는 설계상의 제한사항이 있는 경우 자체 엔진 내 레터박싱을 통해 가로세로 비율을 유지하고 resizeableActivity = false를 선언하고 구성 모드를 피하면서 가능한 최적의 크기로 조정하세요.

어떤 접근 방식을 취하든 다양한 구성에서 게임을 테스트 (접기 및 펼치기, 다양한 회전 변경, 화면 분할 모드)하여 게임 내 UI 요소가 잘리거나 겹치지 않고, 터치 타겟 접근성 문제가 없으며, 게임이 늘어나거나, 압축되거나, 왜곡되는 종횡비 문제가 없는지 확인하세요.

또한 화면이 클수록 픽셀이 커지는 경우가 많습니다. 훨씬 넓은 영역에 동일한 수의 픽셀이 있기 때문입니다. 이로 인해 크기가 축소된 렌더링 버퍼나 해상도가 낮은 애셋에 모자이크 현상이 발생할 수 있습니다. 대형 화면 기기에서 최고 품질의 애셋을 사용하고 게임의 성능 프로필을 작성하여 문제가 없는지 확인하세요. 게임에서 여러 품질 수준을 지원하는 경우 대형 화면 기기를 고려해야 합니다.

멀티 윈도우 모드

멀티 윈도우 모드를 사용하면 여러 앱이 같은 화면을 동시에 공유할 수 있습니다. 멀티 윈도우 모드는 활동 수명 주기를 변경하지 않습니다. 그러나 멀티 윈도우에서 재개되는 앱의 상태는 Android 버전에 따라 다릅니다 (멀티 윈도우 모드 지원의 멀티 윈도우 모드의 활동 수명 주기 참고).

플레이어가 앱이나 게임을 멀티 윈도우 모드로 전환하면 시스템은 고급 대형 화면 크기 조절 가능성 섹션에 지정된 대로 활동에 구성 변경을 알립니다. 플레이어가 게임의 크기를 조절하거나 게임을 전체 화면 모드로 다시 전환할 때도 구성 변경이 발생합니다.

애플리케이션이 멀티 윈도우 모드로 전환될 때 포커스를 다시 얻는다는 보장은 없습니다. 따라서 앱 상태 이벤트를 사용하여 게임을 일시중지하는 경우 포커스 획득 이벤트 (포커스 값이 true인 onWindowFocusChanged())를 사용하여 게임을 재개하지 마세요. 대신 onConfigurationChanged() 또는 onResume()와 같은 다른 이벤트 핸들러나 상태 변경 핸들러를 사용하세요. isInMultiWindowMode() 메서드를 사용하여 현재 활동이 멀티 윈도우 모드로 실행 중인지 항상 감지할 수 있습니다.

Googlebook, ChromeOS, 대형 화면 태블릿 및 폴더블의 데스크톱 창에서는 초기 창 크기가 중요한 고려사항이 됩니다. 게임이 전체 화면일 필요는 없으며 이 경우 창의 크기를 선언해야 합니다. 이 문제를 해결하는 데 권장되는 방법은 두 가지입니다.

첫 번째 옵션은 Android 매니페스트의 <layout> 태그에 특정 속성을 사용하여 작동합니다. defaultHeight 및 defaultWidth 속성은 초기 크기를 제어합니다. 플레이어가 지원하지 않는 크기로 게임 창의 크기를 조절하지 못하도록 minHeight 및 minWidth 속성도 유의하세요. 마지막으로 gravity 속성이 있습니다. 이 속성은 실행 시 화면에 창이 표시되는 위치를 결정합니다. 다음은 이러한 속성을 사용하는 레이아웃 태그의 예입니다.

<layout android:defaultHeight="500dp"
        android:defaultWidth="600dp"
        android:gravity="top|end"
        android:minHeight="450dp"
        android:minWidth="300dp" />

창 크기를 설정하는 두 번째 옵션은 동적 실행 경계를 사용하여 작동합니다. setLaunchBounds(Rect)⁠⁠를 사용하면 시작 창 크기를 정의할 수 있습니다. 빈 사각형이 지정되면 활동이 최대화된 상태로 시작됩니다.

또한 Unity 또는 Unreal 게임 엔진을 사용하는 경우 멀티 윈도우 모드를 잘 지원하는 최신 버전 (Unity 2019.4.40 및 Unreal 5.3 이상)을 사용해야 합니다.

폴더블 자세 지원

Jetpack WindowManager 레이아웃 라이브러리를 사용하여 테이블 모드와 같은 폴더블 자세를 지원하여 플레이어의 몰입도와 참여도를 높입니다.

그림 3. 디스플레이의 세로 부분에 기본 뷰가 표시되고 가로 부분에 컨트롤이 표시된 테이블탑 자세로 게임을 플레이합니다.

Kotlin

fun isTableTopPosture(foldFeature : FoldingFeature?) : Boolean {
    contract { returns(true) implies (foldFeature != null) }
    return foldFeature?.state == FoldingFeature.State.HALF_OPENED &&
            foldFeature.orientation == FoldingFeature.Orientation.HORIZONTAL
}

자바

boolean isTableTopPosture(FoldingFeature foldFeature) {
    return (foldFeature != null) &&
           (foldFeature.getState() == FoldingFeature.State.HALF_OPENED) &&
           (foldFeature.getOrientation() == FoldingFeature.Orientation.HORIZONTAL);
}