如要在遊戲中繪製物件和精靈,您必須設定 顯示、途徑和背景變數,在遊戲迴圈中設定轉譯,以及 繪製每個場景和物件
您可以透過兩種方式,也就是透過 OpenGL ES 或 Vulkan,將 C 或 C++ 遊戲的圖像繪製成螢幕畫面。
OpenGL ES 是 Open Graphics 的一部分 程式庫 (OpenGL®) 規格 專為 Android 等行動裝置設計在這個主題中瞭解如何為遊戲設定 OpenGL ES。
如果您的遊戲使用 Vulkan,請參閱 開始使用 Vulkan 指南。
事前準備
如果您尚未在 Android 專案中設定 GameActivity 物件,請先完成這項作業。
設定 OpenGL ES 變數
您必須有螢幕、途徑、背景和設定才能進行遊戲轉譯。請在遊戲引擎的標頭檔案中加入下列 OpenGL ES 變數:
class NativeEngine { //... private: EGLDisplay mEglDisplay; EGLSurface mEglSurface; EGLContext mEglContext; EGLConfig mEglConfig; bool mHasFocus, mIsVisible, mHasWindow; bool mHasGLObjects; bool mIsFirstFrame; int mSurfWidth, mSurfHeight; }
在遊戲引擎的建構函式中,初始化變數的預設值。
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; }
初始化要轉譯的螢幕。
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; }
途徑可以是 EGL 分配的螢幕外緩衝區 (pbuffer),或 Android OS 分配的視窗。初始化以下途徑:
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; }
初始化轉譯背景。以下範例會建立 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; }
請在繪圖前進行 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); }
透過遊戲迴圈算繪
遊戲迴圈會轉譯影格並無限期重複,直到使用者結束為止。在影格之間,遊戲可能會:
如要在螢幕上轉譯影格,系統會在遊戲迴圈中無限期呼叫
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(); } } }
在
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()); } }
轉譯情境和物件
遊戲迴圈會處理要轉譯的可見情境和物件的階層。 在 Endless Tunnel 範例中,
SceneManager
會追蹤多個情境,一次只能使用一個情境。在以下範例中,已轉譯目前的情境:void SceneManager::DoFrame() { if (mSceneToInstall) { InstallScene(mSceneToInstall); mSceneToInstall = NULL; } if (mHasGraphics && mCurScene) { mCurScene->DoFrame(); } }
視遊戲而定,情境可能包含背景、文字、精靈和遊戲物件。請根據您的遊戲,以適當的順序轉譯這些內容。以下範例轉譯了背景、文字和小工具:
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 回呼及管理執行緒。