대형 화면 크기 조절 지원

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

기본 대형 화면 구성

게임이 크기 조절 가능 여부를 처리할 수 있는지 여부를 선언합니다.

<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 게임즈 그래픽 구성을 참고하세요.

ChromeOS 및 Android 대형 화면

ChromeOS 및 대형 화면 Android 기기에서 게임의 조회 가능 영역을 최대화하려면 decorView에 플래그를 설정하거나 시스템 UI 공개 상태를 설정하거나 WindowInsetsCompat API를 통해 전체 화면 몰입형 모드를 지원하고 시스템 표시줄을 숨깁니다. 또한 회전 및 크기 조절 구성 이벤트를 적절하게 처리하거나 ChromeOS 기기에서 이러한 이벤트가 발생하지 않도록 하는 것이 좋습니다.

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

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

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

고급 대형 화면 크기 조절 가능

그림 2. 데스크톱의 다양한 UI와 탁자 상태의 폴더블

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

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

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

    <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
    }
    

    Java

    @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() 메서드를 사용하여 현재 활동이 멀티 윈도우 모드에서 실행 중인지 감지할 수 있습니다.

ChromeOS의 멀티 윈도우 모드에서는 초기 창 크기가 중요한 고려사항이 됩니다. 게임은 전체 화면이 아니어도 되며 이 경우 창의 크기를 선언하는 것이 좋습니다. 여기에는 두 가지 권장 방법이 있습니다.

첫 번째 옵션은 Android 매니페스트의 <layout> 태그에서 특정 속성을 사용하는 것입니다. defaultHeightdefaultWidth 속성은 초기 크기를 제어합니다. 플레이어가 게임 창의 크기를 개발자가 지원하지 않는 크기로 조정하지 않도록 minHeightminWidth 속성에 유의해야 합니다. 마지막으로 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
}

Java

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