NativeActivity에서 이전   Android Game Development Kit의 일부.

GameActivity는 Android 프레임워크의 NativeActivity를 기반으로 하며 향상된 기능과 새로운 기능을 포함합니다.

  • Jetpack의 Fragment를 지원합니다.
  • 소프트 키보드 통합을 용이하게 하기 위해 TextInput 지원을 추가합니다.
  • NativeActivity onInputEvent 인터페이스가 아닌 GameActivity 자바 클래스의 터치 및 키 이벤트를 처리합니다.

NativeActivity에서 GameActivity로 이전하는 작업은 비교적 간단하며 다음과 같이 애플리케이션을 변경해야 합니다.

  1. 자바 및 C/C++ 빌드 스크립트를 업데이트합니다.
  2. 새 자바 클래스 및 C/C++ 소스 코드를 통합합니다.

이 가이드의 나머지 부분에서는 필요한 단계를 자세히 설명합니다. 이전하기 전에 시작 가이드를 참고하여 GameActivity의 작동 방식에 관한 대략적인 정보를 확인하는 것이 좋습니다.

자바 빌드 스크립트 업데이트

GameActivity는 Jetpack 라이브러리로 배포됩니다. 시작 가이드에 설명된 Gradle 스크립트 업데이트 단계를 적용해야 합니다.

  1. 프로젝트의 gradle.properties 파일에서 Jetpack 라이브러리를 사용 설정합니다.

    android.useAndroidX=true
    
  2. 선택적으로, 동일한 gradle.properties 파일에서 Prefab 버전을 지정합니다. 예를 들면 다음과 같습니다.

    android.prefabVersion=2.0.0
    
  3. 앱의 build.gradle 파일에서 Prefab 기능을 사용 설정합니다.

    android {
        ... // other configurations
        buildFeatures.prefab true
    }
    
  4. 애플리케이션에 GameActivity 종속 항목을 추가합니다.

    1. coregames-activity 라이브러리를 추가합니다.
    2. 현재 지원되는 최소 API 수준이 16 미만인 경우 최소 16으로 업데이트합니다.
    3. 컴파일된 SDK 버전을 games-activity 라이브러리에 필요한 버전으로 업데이트합니다. Jetpack에는 일반적으로 출시 빌드 시간에 최신 SDK 버전이 필요합니다.

    업데이트된 build.gradle 파일은 다음과 같습니다.

    android {
        compiledSdkVersion 31
        ... // other configurations.
        defaultConfig {
            minSdkVersion 16
        }
        ... // other configurations.
    
        buildFeatures.prefab true
    }
    dependencies {
        implementation 'androidx.core:core:1.4.2'
        implementation 'androidx.games:games-activity:1.1.0'
    }
    

Kotlin 또는 자바 코드 업데이트

NativeActivity를 시작 활동으로 사용하여 전체 화면 애플리케이션을 만들 수 있습니다. 현재 GameActivity는 시작 활동으로 사용할 수 없습니다. 앱은 GameActivity에서 클래스를 가져와 시작 활동으로 사용해야 합니다. 또한 추가로 구성을 변경하여 전체 화면 앱을 만들어야 합니다.

다음 단계에서는 애플리케이션에서 NativeActivity를 시작 활동으로 사용한다고 가정합니다. 사용하지 않는다면 단계를 대부분 건너뛰어도 됩니다.

  1. 새 시작 활동을 호스팅하는 Kotlin 또는 자바 파일을 만듭니다. 예를 들어 다음 코드는 시작 활동으로 MainActivity를 만들고 애플리케이션의 기본 네이티브 라이브러리 libAndroidGame.so를 로드합니다.

    Kotlin

    class MainActivity : GameActivity() {
       override fun onResume() {
           super.onResume()
           // Use the function recommended from the following page:
           // https://d.android.com/training/system-ui/immersive
           hideSystemBars()
       }
       companion object {
           init {
               System.loadLibrary("AndroidGame")
           }
       }
    }
    

    자바

      public class MainActivity extends GameActivity {
          protected void onResume() {
              super.onResume();
              // Use the function recommended from
              // https://d.android.com/training/system-ui/immersive
              hideSystemBars();
          }
          static {
              System.loadLibrary("AndroidGame");
          }
      }
    
  2. res\values\themes.xml 파일에서 전체 화면 앱 테마를 만듭니다.

    <resources xmlns:tools="http://schemas.android.com/tools">
        <!-- Base application theme. -->
        <style name="Application.Fullscreen" parent="Theme.AppCompat.Light.NoActionBar">
            <item name="android:windowFullscreen">true</item>
            <item name="android:windowContentOverlay">@null</item>"
        </style>
    </resources>
    
  3. AndroidManifest.xml 파일의 애플리케이션에 이 테마를 적용합니다.

    <application  android:theme=”@style/Application.Fullscreen”>
         <!-- other configurations not listed here. -->
    </application>
    

    전체 화면 모드에 관한 자세한 안내 문서는 몰입형 가이드games-samples 저장소의 구현 예를 참고하세요.

이 이전 가이드에서는 네이티브 라이브러리 이름을 변경하지 않습니다. 이름을 변경하는 경우 네이티브 라이브러리 이름이 다음 세 위치에서 일관되도록 합니다.

  • Kotlin 또는 자바 코드

    System.loadLibrary(“AndroidGame”)
    
  • AndroidManifest.xml:

    <meta-data android:name="android.app.lib_name"
            android:value="AndroidGame" />
    
  • C/C++ 빌드 스크립트 파일 내부(예: CMakeLists.txt)

    add_library(AndroidGame ...)
    

C/C++ 빌드 스크립트 업데이트

이 섹션의 안내에서는 cmake를 예로 사용합니다. 애플리케이션에서 ndk-build를 사용한다면 ndk-build 문서 페이지에 설명된 상응하는 명령어에 매핑해야 합니다.

현재 GameActivity의 C/C++ 구현은 소스 출시 전용이며 prefab 유틸리티와 함께 AAR 내에 제공됩니다. 네이티브 코드에는 GameActivity의 C/C++ 소스와 native_app_glue 코드가 포함됩니다. 애플리케이션의 C/C++ 코드와 함께 빌드해야 합니다.

NativeActivity 애플리케이션은 이미 NDK 내에서 제공되는 native_app_glue 코드를 사용합니다. GameActivity의 native_app_glue 버전으로 대체해야 합니다. 그 외에는 시작 가이드에 설명된 모든 cmake 단계가 적용됩니다.

  • 애플리케이션의 C/C++ 모듈 종속 항목에 game-activity를 추가합니다.

    find_package(game-activity REQUIRED CONFIG)
        ...
    target_link_libraries(AndroidGame game-activity::game-activity)
    
  • 다음과 같이 NDK의 native_app_glue 코드에 대한 모든 참조를 삭제합니다.

    ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c
        ...
    set(CMAKE_SHARED_LINKER_FLAGS
        "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
    
  • GameActivity 소스 파일을 포함합니다.

    get_target_property(game-activity-include
                        game-activity::game-activity
                        INTERFACE_INCLUDE_DIRECTORIES)
    add_library(${PROJECT_NAME} SHARED
        main.cpp
        ${game-activity-include}/game-activity/native_app_glue/android_native_app_glue.c
        ${game-activity-include}/game-activity/GameActivity.cpp
        ${game-activity-include}/game-text-input/gametextinput.cpp)
    

C/C++ 소스 코드 업데이트

다음 단계를 따라 애플리케이션의 NativeActivity 참조를 GameActivity로 바꿉니다.

  • GameActivity와 함께 출시된 native_app_glue를 사용합니다. 모든 android_native_app_glue.h 사용을 검색하여 다음과 같이 바꿉니다.

    #include <game-activity/native_app_glue/android_native_app_glue.h>
    
  • 모션 이벤트 필터와 키 이벤트 필터를 모두 NULL로 설정합니다. 그러면 앱이 모든 입력 기기에서 입력 이벤트를 수신할 수 있습니다. 일반적으로 android_main() 함수 내에서 이 작업을 실행합니다.

    void android_main(android_app* app) {
        ... // other init code.
    
        android_app_set_key_event_filter(app, NULL);
        android_app_set_motion_event_filter(app, NULL);
    
        ... // additional init code, and game loop code.
    }
    
  • AInputEvent 관련 코드를 삭제하고 GameActivity의 InputBuffer 구현으로 바꿉니다.

    while (true) {
        // Read all pending events.
        int events;
        struct android_poll_source* source;
    
        // If not animating, block forever waiting for events.
        // If animating, loop until all events are read, then continue
        // to draw the next frame of animation.
        while ((ALooper_pollAll(engine.animating ? 0 : -1, nullptr, &events,
                                (void**)&source)) >= 0) {
           // Process this app cycle or inset change event.
           if (source) {
               source->process(source->app, source);
           }
    
              ... // Other processing.
    
           // Check if app is exiting.
           if (state->destroyRequested) {
               engine_term_display(&engine);
               return;
           }
        }
        // Process input events if there are any.
        engine_handle_input(state);
    
       if (engine.animating) {
           // Draw a game frame.
       }
    }
    
    // Implement input event handling function.
    static int32_t engine_handle_input(struct android_app* app) {
       auto* engine = (struct engine*)app->userData;
       auto ib = android_app_swap_input_buffers(app);
       if (ib && ib->motionEventsCount) {
           for (int i = 0; i < ib->motionEventsCount; i++) {
               auto *event = &ib->motionEvents[i];
               int32_t ptrIdx = 0;
               switch (event->action & AMOTION_EVENT_ACTION_MASK) {
                   case AMOTION_EVENT_ACTION_POINTER_DOWN:
                   case AMOTION_EVENT_ACTION_POINTER_UP:
                       // Retrieve the index for the starting and the ending of any secondary pointers
                       ptrIdx = (event->action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >>
                                AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
                   case AMOTION_EVENT_ACTION_DOWN:
                   case AMOTION_EVENT_ACTION_UP:
                       engine->state.x = GameActivityPointerAxes_getAxisValue(
                           &event->pointers[ptrIdx], AMOTION_EVENT_AXIS_X);
                       engine->state.y = GameActivityPointerAxes_getAxisValue(
                           &event->pointers[ptrIdx], AMOTION_EVENT_AXIS_Y);
                       break;
                    case AMOTION_EVENT_ACTION_MOVE:
                    // Process the move action: the new coordinates for all active touch pointers
                    // are inside the event->pointers[]. Compare with our internally saved
                    // coordinates to find out which pointers are actually moved. Note that there is
                    // no index embedded inside event->action for AMOTION_EVENT_ACTION_MOVE (there
                    // might be multiple pointers moved at the same time).
                        ...
                       break;
               }
           }
           android_app_clear_motion_events(ib);
       }
    
       // Process the KeyEvent in a similar way.
           ...
    
       return 0;
    }
    
  • NativeActivity의 AInputEvent에 연결된 로직을 검토하고 업데이트합니다. 이전 단계에서 본 바와 같이 GameActivity의 InputBuffer 처리는 ALooper_pollAll() 루프 외부에 있습니다.

  • android_app::activity->clazz 사용을 android_app:: activity->javaGameActivity로 바꿉니다. GameActivity는 자바 GameActivity 인스턴스의 이름을 바꿉니다.

추가 단계

이전 단계에서는 NativeActivity의 기능을 다루지만 GameActivity에는 사용하면 좋은 추가 기능이 포함되어 있습니다.

이러한 기능을 살펴보고 게임에 맞게 적절하게 채택하는 것이 좋습니다.

GameActivity 또는 기타 AGDK 라이브러리에 관한 질문이나 권장사항이 있으면 버그를 작성하여 알려주시기 바랍니다.