Untuk menggambar objek dan sprite dalam game, Anda harus mengonfigurasi variabel layar, permukaan, dan konteks, menyiapkan rendering dalam game loop, serta menggambar setiap scene dan objek.
Untuk game C atau C++, ada dua cara untuk menggambar gambar ke layar, yaitu dengan OpenGL ES atau Vulkan.
OpenGL ES adalah bagian dari spesifikasi Open Graphics Library (OpenGL®) untuk perangkat seluler seperti Android. Pelajari cara mengonfigurasi OpenGL ES untuk game Anda dalam topik ini.
Jika Anda menggunakan Vulkan untuk game, baca Mulai menggunakan Vulkan kami.
Sebelum memulai
Jika Anda belum melakukannya, siapkan objek GameActivity di project Android Anda.
Menyiapkan variabel OpenGL ES
Anda memerlukan layar, platform, konteks, dan konfigurasi untuk merender game Anda. Tambahkan variabel OpenGL ES berikut ke file header mesin game Anda:
class NativeEngine { //... private: EGLDisplay mEglDisplay; EGLSurface mEglSurface; EGLContext mEglContext; EGLConfig mEglConfig; bool mHasFocus, mIsVisible, mHasWindow; bool mHasGLObjects; bool mIsFirstFrame; int mSurfWidth, mSurfHeight; }
Pada konstruktor untuk mesin game Anda, inisialisasi nilai default variabel.
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; }
Inisialisasi layar untuk dirender.
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; }
Platform dapat berupa buffer di luar layar (pbuffer) yang dialokasikan oleh EGL atau jendela yang dialokasikan oleh Android OS. Inisialisasi platform ini:
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; }
Inisialisasi konteks rendering. Contoh ini membuat konteks 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; }
Konfigurasi setelan OpenGL ES sebelum menggambar. Contoh ini dijalankan di awal setiap frame. Hal ini akan memungkinkan pengujian kedalaman, menetapkan warna yang jelas ke hitam, dan menghapus buffer warna dan kedalaman.
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); }
Merender dengan game loop
Game loop merender frame dan mengulanginya tanpa batas waktu hingga pengguna berhenti. Di sela-sela frame, game Anda dapat:
Memproses peristiwa seperti input, output audio, dan peristiwa jaringan.
Memperbarui logika game dan antarmuka pengguna.
Merender frame ke layar.
Untuk merender frame ke layar, metode
DoFrame
dipanggil tanpa batas di game loop: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(); } } }
Pada metode
DoFrame
, buat kueri dimensi platform saat ini, mintaSceneManager
untuk merender frame, dan menukar buffer display.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()); } }
Merender scene dan objek
Game loop memproses hierarki scene dan objek yang terlihat untuk dirender. Pada contoh Endless Tunnel,
SceneManager
akan melacak beberapa scene, dengan hanya 1 scene yang aktif dalam satu waktu. Dalam contoh ini, scene saat ini dirender:void SceneManager::DoFrame() { if (mSceneToInstall) { InstallScene(mSceneToInstall); mSceneToInstall = NULL; } if (mHasGraphics && mCurScene) { mCurScene->DoFrame(); } }
Bergantung pada game Anda, scene dapat berisi objek latar belakang, teks, sprite, dan game. Lakukan render dalam urutan yang sesuai untuk game Anda. Contoh ini merender latar belakang, teks, dan widget:
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); }
Referensi
Baca informasi berikut untuk mengetahui informasi OpenGL ES dan Vulkan selengkapnya:
OpenGL ES - Gambar dan grafis di Android.
OpenGL ES - Ringkasan di Sumber Android.
Vulkan - Memulai di NDK.
Vulkan - Ringkasan di Sumber Android.
Memahami game loop Android - pelajari cara mengatur kecepatan frame, mengantre buffer, menangani callback VSYNC, dan mengelola thread.