অ্যান্ড্রয়েড গেম ডেভেলপমেন্ট কিট- এর অংশ গেমঅ্যাক্টিভিটি (GameActivity) দিয়ে শুরু করুন।

এই নির্দেশিকায় আপনার অ্যান্ড্রয়েড গেমে GameActivity কীভাবে সেট আপ ও ইন্টিগ্রেট করতে হয় এবং ইভেন্টগুলি কীভাবে পরিচালনা করতে হয়, তা বর্ণনা করা হয়েছে।

GameActivity গুরুত্বপূর্ণ এপিআই (API) ব্যবহারের প্রক্রিয়াকে সহজ করার মাধ্যমে আপনার সি (C) বা সি++ (C++) গেমকে অ্যান্ড্রয়েডে নিয়ে আসতে সাহায্য করে। পূর্বে গেমের জন্য NativeActivity ক্লাসটিই সুপারিশ করা হতো। GameActivity এখন গেমের জন্য সুপারিশকৃত ক্লাস হিসেবে এটিকে প্রতিস্থাপন করেছে এবং এটি এপিআই লেভেল ১৯ (API level 19) পর্যন্ত ব্যাকওয়ার্ড কম্প্যাটিবল।

GameActivity সমন্বিত একটি নমুনার জন্য, games-samples রিপোজিটরিটি দেখুন।

শুরু করার আগে

ডিস্ট্রিবিউশন পেতে GameActivity রিলিজগুলো দেখুন।

আপনার বিল্ড সেট আপ করুন

অ্যান্ড্রয়েডে, একটি Activity আপনার গেমের প্রবেশদ্বার হিসেবে কাজ করে এবং এর ভেতরে আঁকার জন্য Window সরবরাহ করে। অনেক গেম NativeActivity এর সীমাবদ্ধতাগুলো কাটিয়ে ওঠার জন্য এই Activity তাদের নিজস্ব জাভা বা কোটলিন ক্লাস দিয়ে প্রসারিত করে এবং একই সাথে তাদের C বা C++ গেম কোডের সাথে সংযোগ স্থাপনের জন্য JNI কোড ব্যবহার করে।

GameActivity নিম্নলিখিত ক্ষমতাগুলো প্রদান করে:

  • AppCompatActivity থেকে উত্তরাধিকার সূত্রে প্রাপ্ত হওয়ায়, এটি আপনাকে অ্যান্ড্রয়েড জেটপ্যাক আর্কিটেকচার কম্পোনেন্ট ব্যবহার করার সুযোগ দেয়।

  • এটি একটি SurfaceView রেন্ডার হয়, যা আপনাকে অন্য যেকোনো অ্যান্ড্রয়েড UI উপাদানের সাথে ইন্টারফেস করার সুযোগ দেয়।

  • জাভা অ্যাক্টিভিটির ইভেন্টগুলো পরিচালনা করে। এর ফলে যেকোনো অ্যান্ড্রয়েড UI এলিমেন্ট (যেমন একটি EditText , একটি WebView বা একটি Ad ) একটি C ইন্টারফেসের মাধ্যমে আপনার গেমে যুক্ত করা যায়।

  • NativeActivity এর অনুরূপ একটি C API এবং android_native_app_glue লাইব্রেরি প্রদান করে।

GameActivity একটি অ্যান্ড্রয়েড আর্কাইভ (AAR) হিসেবে বিতরণ করা হয়। এই AAR-টিতে সেই জাভা ক্লাসটি থাকে যা আপনি আপনার AndroidManifest.xml এ ব্যবহার করেন, সেইসাথে C এবং C++ সোর্স কোডও থাকে যা GameActivity এর জাভা অংশকে অ্যাপের C/C++ ইমপ্লিমেন্টেশনের সাথে সংযুক্ত করে। আপনি যদি GameActivity 1.2.2 বা তার পরবর্তী সংস্করণ ব্যবহার করেন, তাহলে C/C++ স্ট্যাটিক লাইব্রেরিও সরবরাহ করা হয়। যখনই প্রযোজ্য, আমরা সোর্স কোডের পরিবর্তে স্ট্যাটিক লাইব্রেরি ব্যবহার করার পরামর্শ দিই।

Prefab মাধ্যমে এই সোর্স ফাইলগুলো বা স্ট্যাটিক লাইব্রেরিটিকে আপনার বিল্ড প্রক্রিয়ার অংশ হিসেবে অন্তর্ভুক্ত করুন, যা আপনার CMake প্রজেক্ট বা NDK বিল্ড-এর জন্য নেটিভ লাইব্রেরি ও সোর্স কোড উন্মুক্ত করে।

  1. আপনার গেমের build.gradle ফাইলে GameActivity লাইব্রেরি ডিপেন্ডেন্সি যোগ করতে Jetpack Android Games পৃষ্ঠার নির্দেশাবলী অনুসরণ করুন।

  2. অ্যান্ড্রয়েড প্লাগইন সংস্করণ (AGP) 4.1+ এর সাথে নিম্নলিখিত পদক্ষেপগুলি অনুসরণ করে প্রিফ্যাব সক্রিয় করুন:

    • আপনার মডিউলের build.gradle ফাইলের android ব্লকে নিম্নলিখিতটি যোগ করুন:
    buildFeatures {
        prefab true
    }
    
    android.prefabVersion=2.0.0
    

    আপনি যদি AGP-এর পূর্ববর্তী সংস্করণগুলো ব্যবহার করেন, তাহলে সংশ্লিষ্ট কনফিগারেশন নির্দেশাবলীর জন্য প্রিফ্যাব ডকুমেন্টেশন অনুসরণ করুন।

  3. নিম্নলিখিতভাবে আপনার প্রজেক্টে C/C++ স্ট্যাটিক লাইব্রেরি অথবা C/++ সোর্স কোড ইম্পোর্ট করুন।

    স্থির গ্রন্থাগার

    আপনার প্রোজেক্টের CMakeLists.txt ফাইলে, game-activity_static প্রিফ্যাব মডিউলের মধ্যে game-activity স্ট্যাটিক লাইব্রেরিটি ইম্পোর্ট করুন:

    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)
    

    এছাড়াও, আপনার প্রোজেক্টের CmakeLists.txt ফাইলে নিম্নলিখিত ফাইলগুলি অন্তর্ভুক্ত করুন: GameActivity.cpp , GameTextInput.cpp , এবং android_native_app_glue.c

অ্যান্ড্রয়েড কীভাবে আপনার অ্যাক্টিভিটি চালু করে

অ্যান্ড্রয়েড সিস্টেম আপনার অ্যাক্টিভিটি ইনস্ট্যান্সে কোড এক্সিকিউট করে অ্যাক্টিভিটি লাইফসাইকেলের নির্দিষ্ট ধাপগুলোর সাথে সম্পর্কিত কলব্যাক মেথডগুলোকে কল করার মাধ্যমে। অ্যান্ড্রয়েড যাতে আপনার অ্যাক্টিভিটি চালু করে গেম শুরু করতে পারে, তার জন্য আপনাকে অ্যান্ড্রয়েড ম্যানিফেস্টে উপযুক্ত অ্যাট্রিবিউটসহ আপনার অ্যাক্টিভিটি ডিক্লেয়ার করতে হবে। আরও তথ্যের জন্য, অ্যাক্টিভিটি পরিচিতি দেখুন।

অ্যান্ড্রয়েড ম্যানিফেস্ট

প্রতিটি অ্যাপ প্রজেক্টের সোর্স সেটের রুটে অবশ্যই একটি AndroidManifest.xml ফাইল থাকতে হবে। ম্যানিফেস্ট ফাইলটি অ্যান্ড্রয়েড বিল্ড টুলস, অ্যান্ড্রয়েড অপারেটিং সিস্টেম এবং গুগল প্লে-কে আপনার অ্যাপ সম্পর্কে প্রয়োজনীয় তথ্য বর্ণনা করে। এর মধ্যে অন্তর্ভুক্ত রয়েছে:

আপনার গেমে GameActivity প্রয়োগ করুন

  1. আপনার প্রধান অ্যাক্টিভিটি জাভা ক্লাসটি তৈরি বা শনাক্ত করুন (যেটি আপনার AndroidManifest.xml ফাইলের ভেতরের activity এলিমেন্টে নির্দিষ্ট করা আছে)। এই ক্লাসটিকে com.google.androidgamesdk প্যাকেজের GameActivity এক্সটেন্ড করার জন্য পরিবর্তন করুন:

    import com.google.androidgamesdk.GameActivity;
    
    public class YourGameActivity extends GameActivity { ... }
    
  2. একটি স্ট্যাটিক ব্লক ব্যবহার করে নিশ্চিত করুন যে আপনার নেটিভ লাইব্রেরিটি শুরুতে লোড হয়:

    public class EndlessTunnelActivity extends GameActivity {
      static {
        // Load the native library.
        // The name "android-game" depends on your CMake configuration, must be
        // consistent here and inside AndroidManifect.xml
        System.loadLibrary("android-game");
      }
      ...
    }
    
  3. আপনার লাইব্রেরির নাম ডিফল্ট নাম ( libmain.so ) না হলে, আপনার নেটিভ লাইব্রেরিটি AndroidManifest.xml এ যোগ করুন:

    <meta-data android:name="android.app.lib_name"
     android:value="android-game" />
    

অ্যান্ড্রয়েড_মেইন বাস্তবায়ন করুন

  1. android_native_app_glue লাইব্রেরিটি একটি সোর্স কোড লাইব্রেরি যা আপনার গেম, মেইন থ্রেডে ব্লকিং প্রতিরোধ করার জন্য একটি পৃথক থ্রেডে GameActivity লাইফসাইকেল ইভেন্টগুলো পরিচালনা করতে ব্যবহার করে। লাইব্রেরিটি ব্যবহার করার সময়, টাচ ইনপুট ইভেন্টের মতো লাইফসাইকেল ইভেন্টগুলো হ্যান্ডেল করার জন্য আপনাকে কলব্যাকটি রেজিস্টার করতে হয়। GameActivity আর্কাইভে android_native_app_glue লাইব্রেরির নিজস্ব সংস্করণ অন্তর্ভুক্ত থাকে, তাই আপনি NDK রিলিজে অন্তর্ভুক্ত সংস্করণটি ব্যবহার করতে পারবেন না। যদি আপনার গেমগুলো NDK-তে অন্তর্ভুক্ত android_native_app_glue লাইব্রেরিটি ব্যবহার করে থাকে, তবে GameActivity সংস্করণে সুইচ করুন।

    আপনার প্রজেক্টে android_native_app_glue লাইব্রেরির সোর্স কোড যোগ করার পর, এটি GameActivity সাথে ইন্টারফেস করে। android_main নামে একটি ফাংশন ইমপ্লিমেন্ট করুন, যেটিকে লাইব্রেরি কল করে এবং যা আপনার গেমের এন্ট্রি পয়েন্ট হিসেবে ব্যবহৃত হয়। এটিকে android_app নামের একটি স্ট্রাকচার পাস করা হয়। এটি আপনার গেম এবং ইঞ্জিনের জন্য ভিন্ন হতে পারে। এখানে একটি উদাহরণ দেওয়া হলো:

    #include <game-activity/native_app_glue/android_native_app_glue.h>
    
    extern "C" {
        void android_main(struct android_app* state);
    };
    
    void android_main(struct android_app* app) {
        NativeEngine *engine = new NativeEngine(app);
        engine->GameLoop();
        delete engine;
    }
    
  2. আপনার প্রধান গেম লুপে android_app প্রসেস করুন, যেমন NativeAppGlueAppCmd- এ সংজ্ঞায়িত অ্যাপ সাইকেল ইভেন্টগুলো পোলিং এবং হ্যান্ডেল করা। উদাহরণস্বরূপ, নিম্নলিখিত কোড স্নিপেটটি _hand_cmd_proxy ফাংশনটিকে NativeAppGlueAppCmd ​​হ্যান্ডলার হিসেবে রেজিস্টার করে, তারপর অ্যাপ সাইকেল ইভেন্টগুলো পোল করে এবং প্রসেসিংয়ের জন্য সেগুলোকে রেজিস্টার করা হ্যান্ডলারের কাছে ( android_app::onAppCmd এ) পাঠায়:

    void NativeEngine::GameLoop() {
      mApp->userData = this;
      mApp->onAppCmd = _handle_cmd_proxy;  // register your command handler.
      mApp->textInputState = 0;
    
      while (1) {
        int events;
        struct android_poll_source* source;
    
        // If not animating, block until we get an event;
        // If animating, don't block.
        while ((ALooper_pollOnce(IsAnimating() ? 0 : -1, NULL, &events,
          (void **) &source)) >= 0) {
            if (source != NULL) {
                // process events, native_app_glue internally sends the outstanding
                // application lifecycle events to mApp->onAppCmd.
                source->process(source->app, source);
            }
            if (mApp->destroyRequested) {
                return;
            }
        }
        if (IsAnimating()) {
            DoFrame();
        }
      }
    }
    
  3. আরও বিস্তারিত জানতে, Endless Tunnel NDK উদাহরণটির বাস্তবায়ন অধ্যয়ন করুন। মূল পার্থক্যটি হবে ইভেন্টগুলি কীভাবে পরিচালনা করতে হয়, যা পরবর্তী বিভাগে দেখানো হয়েছে।

ইভেন্টগুলি পরিচালনা করুন

আপনার অ্যাপে ইনপুট ইভেন্টগুলো পৌঁছানোর জন্য, android_app_set_motion_event_filter এবং android_app_set_key_event_filter ব্যবহার করে আপনার ইভেন্ট ফিল্টারগুলো তৈরি ও রেজিস্টার করুন। ডিফল্টরূপে, native_app_glue লাইব্রেরি শুধুমাত্র SOURCE_TOUCHSCREEN ইনপুট থেকে আসা মোশন ইভেন্টগুলোকেই অনুমোদন করে। বিস্তারিত জানতে রেফারেন্স ডকুমেন্টেশন এবং android_native_app_glue ইমপ্লিমেন্টেশন কোড দেখে নিতে ভুলবেন না।

ইনপুট ইভেন্টগুলো পরিচালনা করতে, আপনার গেম লুপে android_app_swap_input_buffers() ফাংশন ব্যবহার করে android_input_buffer এর একটি রেফারেন্স নিন। শেষবার পোল করার পর থেকে ঘটে যাওয়া মোশন ইভেন্ট এবং কী ইভেন্টগুলো এতে জমা থাকে। এতে থাকা ইভেন্টের সংখ্যা যথাক্রমে motionEventsCount এবং keyEventsCount এ সংরক্ষিত থাকে।

  1. আপনার গেম লুপে প্রতিটি ইভেন্টকে পুনরাবৃত্তি করুন এবং পরিচালনা করুন। এই উদাহরণে, নিম্নলিখিত কোডটি motionEvents পুনরাবৃত্তি করে এবং handle_event এর মাধ্যমে সেগুলিকে পরিচালনা করে:

    android_input_buffer* inputBuffer = android_app_swap_input_buffers(app);
    if (inputBuffer && inputBuffer->motionEventsCount) {
        for (uint64_t i = 0; i < inputBuffer->motionEventsCount; ++i) {
            GameActivityMotionEvent* motionEvent = &inputBuffer->motionEvents[i];
    
            if (motionEvent->pointerCount > 0) {
                const int action = motionEvent->action;
                const int actionMasked = action & AMOTION_EVENT_ACTION_MASK;
                // Initialize pointerIndex to the max size, we only cook an
                // event at the end of the function if pointerIndex is set to a valid index range
                uint32_t pointerIndex = GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT;
                struct CookedEvent ev;
                memset(&ev, 0, sizeof(ev));
                ev.motionIsOnScreen = motionEvent->source == AINPUT_SOURCE_TOUCHSCREEN;
                if (ev.motionIsOnScreen) {
                    // use screen size as the motion range
                    ev.motionMinX = 0.0f;
                    ev.motionMaxX = SceneManager::GetInstance()->GetScreenWidth();
                    ev.motionMinY = 0.0f;
                    ev.motionMaxY = SceneManager::GetInstance()->GetScreenHeight();
                }
    
                switch (actionMasked) {
                    case AMOTION_EVENT_ACTION_DOWN:
                        pointerIndex = 0;
                        ev.type = COOKED_EVENT_TYPE_POINTER_DOWN;
                        break;
                    case AMOTION_EVENT_ACTION_POINTER_DOWN:
                        pointerIndex = ((action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK)
                                       >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
                        ev.type = COOKED_EVENT_TYPE_POINTER_DOWN;
                        break;
                    case AMOTION_EVENT_ACTION_UP:
                        pointerIndex = 0;
                        ev.type = COOKED_EVENT_TYPE_POINTER_UP;
                        break;
                    case AMOTION_EVENT_ACTION_POINTER_UP:
                        pointerIndex = ((action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK)
                                       >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
                        ev.type = COOKED_EVENT_TYPE_POINTER_UP;
                        break;
                    case AMOTION_EVENT_ACTION_MOVE: {
                        // Move includes all active pointers, so loop and process them here,
                        // we do not set pointerIndex since we are cooking the events in
                        // this loop rather than at the bottom of the function
                        ev.type = COOKED_EVENT_TYPE_POINTER_MOVE;
                        for (uint32_t i = 0; i < motionEvent->pointerCount; ++i) {
                            _cookEventForPointerIndex(motionEvent, callback, ev, i);
                        }
                        break;
                    }
                    default:
                        break;
                }
    
                // Only cook an event if we set the pointerIndex to a valid range, note that
                // move events cook above in the switch statement.
                if (pointerIndex != GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT) {
                    _cookEventForPointerIndex(motionEvent, callback,
                                              ev, pointerIndex);
                }
            }
        }
        android_app_clear_motion_events(inputBuffer);
    }
    

    _cookEventForPointerIndex() এবং অন্যান্য সম্পর্কিত ফাংশনগুলোর বাস্তবায়নের জন্য GitHub নমুনাটি দেখুন।

  2. আপনার কাজ শেষ হলে, এইমাত্র সম্পন্ন করা ইভেন্টগুলোর সারিটি খালি করতে মনে রাখবেন:

    android_app_clear_motion_events(mApp);
    

অতিরিক্ত সম্পদ

GameActivity সম্পর্কে আরও জানতে, নিম্নলিখিতগুলি দেখুন:

GameActivity-তে বাগ রিপোর্ট করতে বা নতুন ফিচারের অনুরোধ জানাতে, GameActivity ইস্যু ট্র্যাকার ব্যবহার করুন।