대형 화면 크기 조절 지원

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

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

ChromeOS 및 Android 대형 화면

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

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

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

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

대형 화면 크기 조정 기능 고급

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

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

  1. 기본 활동의 크기 조절이 가능하다고 선언합니다.

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

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

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