إعداد الرسومات باستخدام OpenGL ES

لرسم كائنات ورسومات متحركة في لعبتك، ستحتاج إلى تهيئة عرض متغيّرات العرض وإظهارها والسياق، وإعداد العرض في حلقة الألعاب، لرسم كل مشهد وكائن.

هناك طريقتان لرسم صور على الشاشة للعبة C أو C++، وبالتحديد OpenGL ES أو Vulkan

قبل البدء

إذا لم تكن قد قمت بذلك بالفعل، إعداد كائن GameActivity في مشروع Android.

إعداد متغيّرات OpenGL ES

  1. ستحتاج إلى شاشة السطح، السياق، الضبط لعرض لعبتك. إضافة متغيّرات OpenGL ES التالية في ملف العنوان الخاص بمحرّك اللعبة:

    class NativeEngine {
     //...
     private:
      EGLDisplay mEglDisplay;
      EGLSurface mEglSurface;
      EGLContext mEglContext;
      EGLConfig mEglConfig;
    
      bool mHasFocus, mIsVisible, mHasWindow;
      bool mHasGLObjects;
      bool mIsFirstFrame;
    
      int mSurfWidth, mSurfHeight;
    }
    
  2. في الدالة الإنشائية لمحرك لعبتك، قم بتهيئة القيم الافتراضية المتغيرات.

    NativeEngine::NativeEngine(struct android_app *app) {
      //...
      mEglDisplay = EGL_NO_DISPLAY;
      mEglSurface = EGL_NO_SURFACE;
      mEglContext = EGL_NO_CONTEXT;
      mEglConfig = 0;
    
      mHasFocus = mIsVisible = mHasWindow = false;
      mHasGLObjects = false;
      mIsFirstFrame = true;
    
      mSurfWidth = mSurfHeight = 0;
    }
    
  3. تهيئة العرض ليتم عرضه.

    bool NativeEngine::InitDisplay() {
      if (mEglDisplay != EGL_NO_DISPLAY) {
        return true;
      }
    
      mEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
      if (EGL_FALSE == eglInitialize(mEglDisplay, 0, 0)) {
        LOGE("NativeEngine: failed to init display, error %d", eglGetError());
        return false;
      }
      return true;
    }
    
  4. يمكن أن يكون السطح مخزنًا مؤقتًا خارج الشاشة (pتخزين مؤقت) مخصص بواسطة EGL أو يخصصه نظام التشغيل Android. تهيئة هذا السطح:

    bool NativeEngine::InitSurface() {
      ASSERT(mEglDisplay != EGL_NO_DISPLAY);
      if (mEglSurface != EGL_NO_SURFACE) {
        return true;
      }
    
      EGLint numConfigs;
      const EGLint attribs[] = {
        EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, // request OpenGL ES 2.0
        EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
        EGL_BLUE_SIZE, 8,
        EGL_GREEN_SIZE, 8,
        EGL_RED_SIZE, 8,
        EGL_DEPTH_SIZE, 16,
        EGL_NONE
      };
    
      // Pick the first EGLConfig that matches.
      eglChooseConfig(mEglDisplay, attribs, &mEglConfig, 1, &numConfigs);
      mEglSurface = eglCreateWindowSurface(mEglDisplay, mEglConfig, mApp->window,
                                           NULL);
      if (mEglSurface == EGL_NO_SURFACE) {
        LOGE("Failed to create EGL surface, EGL error %d", eglGetError());
        return false;
      }
      return true;
    }
    
  5. تهيئة سياق العرض. ينشئ هذا المثال سياق OpenGL ES 2.0:

    bool NativeEngine::InitContext() {
      ASSERT(mEglDisplay != EGL_NO_DISPLAY);
      if (mEglContext != EGL_NO_CONTEXT) {
        return true;
      }
    
      // OpenGL ES 2.0
      EGLint attribList[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };
      mEglContext = eglCreateContext(mEglDisplay, mEglConfig, NULL, attribList);
      if (mEglContext == EGL_NO_CONTEXT) {
        LOGE("Failed to create EGL context, EGL error %d", eglGetError());
        return false;
      }
      return true;
    }
    
  6. اضبط إعدادات OpenGL ES قبل الرسم. تم تنفيذ هذا المثال في بداية كل إطار. ويتيح اختبار العمق، ويضبط اللون الواضح على بالأسود، ويمحو المساحات الاحتياطية للألوان والعمق.

    void NativeEngine::ConfigureOpenGL() {
      glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
      glEnable(GL_DEPTH_TEST);
      glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
    }
    

العرض مع حلقة الألعاب

  1. تعرض حلقة الألعاب إطارًا وتتكرر إلى أجل غير مسمى إلى أن يستقيل المستخدم. بين اللقطات، يمكن أن:

    لعرض إطار على الشاشة، تُسمى الطريقة DoFrame. إلى أجل غير مسمى في حلقة الألعاب:

    void NativeEngine::GameLoop() {
      // Loop indefinitely.
      while (1) {
        int events;
        struct android_poll_source* source;
    
        // If not animating, block until we get an event.
        while ((ALooper_pollAll(IsAnimating() ? 0 : -1, NULL, &events,
                                (void **) &source)) >= 0) {
          // Process events.
          ...
        }
    
        // Render a frame.
        if (IsAnimating()) {
            DoFrame();
        }
      }
    }
    
  2. في طريقة DoFrame، استعلم عن أبعاد مساحة العرض الحالية واطلب SceneManager لعرض إطار وتبديل المخازن المؤقتة للعرض.

    void NativeEngine::DoFrame() {
      ...
      // Query the current surface dimension.
      int width, height;
      eglQuerySurface(mEglDisplay, mEglSurface, EGL_WIDTH, &width);
      eglQuerySurface(mEglDisplay, mEglSurface, EGL_HEIGHT, &height);
    
      // Handle dimension changes.
      SceneManager *mgr = SceneManager::GetInstance();
      if (width != mSurfWidth || height != mSurfHeight) {
        mSurfWidth = width;
        mSurfHeight = height;
        mgr->SetScreenSize(mSurfWidth, mSurfHeight);
        glViewport(0, 0, mSurfWidth, mSurfHeight);
      }
      ...
      // Render scenes and objects.
      mgr->DoFrame();
    
      // Swap buffers.
      if (EGL_FALSE == eglSwapBuffers(mEglDisplay, mEglSurface)) {
        HandleEglError(eglGetError());
      }
    }
    

عرض مشاهد وكائنات

  1. وتعالج حلقة الألعاب تسلسلاً هرميًا للمشاهد والعناصر المرئية التي يمكن عرضها. في مثال Endless Tunnel، تتعقب SceneManager مشاهد متعددة، مع مشهد واحد فقط نشط في كل مرة في هذا المثال، المشهد الحالي المعروضة:

    void SceneManager::DoFrame() {
      if (mSceneToInstall) {
        InstallScene(mSceneToInstall);
        mSceneToInstall = NULL;
      }
    
      if (mHasGraphics && mCurScene) {
        mCurScene->DoFrame();
      }
    }
    
  2. استنادًا إلى لعبتك، قد يحتوي المشهد على خلفية أو نص أو صور متحركة كائنات الألعاب. يمكنك عرضها بالترتيب الذي يناسب لعبتك. هذا المثال لعرض الخلفية والنص والتطبيقات المصغّرة:

    void UiScene::DoFrame() {
      // clear screen
      glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
      glDisable(GL_DEPTH_TEST);
    
      RenderBackground();
    
      // Render the "Please Wait" sign and do nothing else
      if (mWaitScreen) {
        SceneManager *mgr = SceneManager::GetInstance();
        mTextRenderer->SetFontScale(WAIT_SIGN_SCALE);
        mTextRenderer->SetColor(1.0f, 1.0f, 1.0f);
        mTextRenderer->RenderText(S_PLEASE_WAIT, mgr->GetScreenAspect() * 0.5f,
                                  0.5f);
        glEnable(GL_DEPTH_TEST);
        return;
      }
    
      // Render all the widgets.
      for (int i = 0; i < mWidgetCount; ++i) {
        mWidgets[i]->Render(mTrivialShader, mTextRenderer, mShapeRenderer,
              (mFocusWidget < 0) ? UiWidget::FOCUS_NOT_APPLICABLE :
              (mFocusWidget == i) ? UiWidget::FOCUS_YES : UiWidget::FOCUS_NO,tf);
      }
      glEnable(GL_DEPTH_TEST);
    }
    

المراجع

يمكنك قراءة ما يلي للحصول على مزيد من المعلومات حول OpenGL ES وVulkan:

  • OpenGL ES - الصور والرسومات في Android.

  • OpenGL ES - نظرة عامة في مصدر Android.

  • Vulkan - الخطوات الأولى في NDK.

  • Vulkan - نظرة عامة في مصدر Android.

  • التعرّف على تكرارات ألعاب Android: تعلّم إيقاع اللعبة الإطارات، والمستودعات الاحتياطية في قائمة الانتظار، والتعامل مع استدعاءات VSYNC، وإدارة سلاسل المحادثات.