NativeActivity에서 이전   Android Game Development Kit에 포함되어 있음

이 페이지에서는 Android 게임 프로젝트의 NativeActivity에서 GameActivity로 이전하는 방법을 설명합니다.

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

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

이전하기 전에 프로젝트에서 GameActivity를 설정하고 통합하는 방법을 설명하는 시작 가이드를 읽어보는 것이 좋습니다.

Java 빌드 스크립트 업데이트

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 33
        ... // other configurations.
        defaultConfig {
            minSdkVersion 16
        }
        ... // other configurations.
    
        buildFeatures.prefab true
    }
    dependencies {
        implementation 'androidx.core:core:1.9.0'
        implementation 'androidx.games:games-activity:1.2.2'
    }
    

Kotlin 또는 Java 코드 업데이트

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

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

  1. 새 시작 활동을 호스팅하는 Kotlin 또는 Java 파일을 만듭니다. 예를 들어 다음 코드는 시작 활동으로 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")
           }
       }
    }
    

    Java

      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 또는 Java 코드

    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++ 구현은 소스 코드 출시를 제공하고 있습니다. 버전 1.2.2 이상의 경우 정적 라이브러리 출시가 제공됩니다. 정적 라이브러리는 권장되는 출시 유형입니다.

이 출시는 prefab 유틸리티와 함께 AAR 내에 패키징됩니다. 네이티브 코드에는 GameActivity의 C/C++ 소스와 native_app_glue 코드가 포함됩니다. 애플리케이션의 C/C++ 코드와 함께 빌드해야 합니다.

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

  • C/C++ 정적 라이브러리나 C/++ 소스 코드를 다음과 같이 프로젝트로 가져옵니다.

    정적 라이브러리

    프로젝트의 CMakeLists.txt 파일에서 game-activity 정적 라이브러리를 game-activity_static prefab 모듈로 가져옵니다.

    find_package(game-activity REQUIRED CONFIG)
    target_link_libraries(${PROJECT_NAME} PUBLIC log android
    game-activity::game-activity_static)
    

    소스 코드

    프로젝트의 CMakeLists.txt 파일에서 game-activity 패키지를 가져와서 대상에 추가합니다. game-activity 패키지에는 libandroid.so가 필요하므로, 없는 경우 가져와야 합니다.

    find_package(game-activity REQUIRED CONFIG)
    ...
    target_link_libraries(... android 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)
    

UnsatisfiedLinkError 문제 해결

com.google.androidgamesdk.GameActivity.initializeNativeCode() 함수에서 UnsatsifiedLinkError가 발생하면 다음 코드를 CMakeLists.txt 파일에 추가하세요.

set(CMAKE_SHARED_LINKER_FLAGS
    "${CMAKE_SHARED_LINKER_FLAGS} -u \
    Java_com_google_androidgamesdk_GameActivity_initializeNativeCode")

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는 Java GameActivity 인스턴스의 이름을 바꿉니다.

추가 단계

이전 단계에서는 NativeActivity의 기능을 다루었으나 GameActivity는 사용하면 좋은 추가 기능을 제공합니다.

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

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