Per disegnare oggetti e sprite nel tuo gioco, devi configurare le variabili di visualizzazione, superficie e contesto, impostare il rendering nel ciclo di gioco e disegnare ogni scena e ciascun oggetto.
Esistono due modi per disegnare le immagini sullo schermo in un gioco C o C++, ovvero con OpenGL ES o Vulkan.
OpenGL ES fa parte della specifica Open Graphics Library (OpenGL®) destinata a dispositivi mobili come Android. Scopri come configurare OpenGL ES per il tuo gioco in questo argomento.
Se usi Vulkan per il tuo gioco, leggi la guida Guida introduttiva a Vulkan.
Prima di iniziare
Se non l'hai ancora fatto, configura un oggetto GameActivity nel progetto Android.
Impostare le variabili OpenGL ES
Per eseguire il rendering del tuo gioco, sono necessari display, superficie, contesto e configurazione. Aggiungi le seguenti variabili OpenGL ES al file di intestazione del motore di gioco:
class NativeEngine { //... private: EGLDisplay mEglDisplay; EGLSurface mEglSurface; EGLContext mEglContext; EGLConfig mEglConfig; bool mHasFocus, mIsVisible, mHasWindow; bool mHasGLObjects; bool mIsFirstFrame; int mSurfWidth, mSurfHeight; }
Nel costruttore del motore di gioco, inizializza i valori predefiniti per le variabili.
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; }
Inizializza il display per il rendering.
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; }
La superficie può essere un buffer fuori schermo (pbuffer) allocato da EGL o una finestra allocata dal sistema operativo Android. Inizializza questa piattaforma:
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; }
Inizializza il contesto di rendering. Questo esempio crea un contesto 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; }
Configura le impostazioni OpenGL ES prima di disegnare. Questo esempio viene eseguito all'inizio di ogni frame. Consente di eseguire test di profondità, imposta il colore chiaro sul nero e cancella i buffer di colore e profondità.
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); }
Rendering con il ciclo di gioco
Il ciclo di gioco esegue il rendering di un frame e si ripete all'infinito fino alla chiusura dell'utente. Tra un frame e l'altro, il gioco potrebbe:
Eventi di processo come input, output audio ed eventi di rete.
Aggiorna la logica e l'interfaccia utente del gioco.
Eseguire il rendering di un frame sul display.
Per eseguire il rendering di un frame sul display, il metodo
DoFrame
viene chiamato in modo indeterminato nel ciclo di gioco: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(); } } }
Nel metodo
DoFrame
, esegui una query sulle dimensioni attuali della superficie, richiediSceneManager
di eseguire il rendering di un frame e scambia i buffer del 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()); } }
Eseguire il rendering di scene e oggetti
Il ciclo di gioco elabora una gerarchia di scene e oggetti visibili da visualizzare. Nell'esempio di Endless Tunnel, un elemento
SceneManager
tiene traccia di più scene, con una sola scena attiva alla volta. In questo esempio, viene visualizzata la scena corrente:void SceneManager::DoFrame() { if (mSceneToInstall) { InstallScene(mSceneToInstall); mSceneToInstall = NULL; } if (mHasGraphics && mCurScene) { mCurScene->DoFrame(); } }
A seconda del gioco, una scena potrebbe contenere sfondo, testo, sprite e oggetti di gioco. Esegui il rendering nell'ordine adatto al gioco. In questo esempio vengono visualizzati lo sfondo, il testo e i 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); }
Risorse
Leggi quanto segue per avere ulteriori informazioni su OpenGL ES e Vulkan:
OpenGL ES: immagini e grafica in Android.
OpenGL ES - Panoramica in Android Source.
Vulkan: come iniziare a NDK.
Vulkan - Panoramica in Android Source.
Comprendi i cicli di gioco Android: scopri come regolare il pacing dei frame, i buffer delle code, la gestione dei callback VSYNC e i thread.