Wenn du in deinem Spiel Objekte und Sprites zeichnen möchtest, musst du die Display-, Oberflächen- und Kontextvariablen, richten Sie das Rendering in Ihrer Spielschleife ein jede Szene und jedes Objekt.
Es gibt zwei Möglichkeiten, in einem C- oder C++-Spiel Bilder auf den Bildschirm zu ziehen: OpenGL ES oder Vulkan.
OpenGL ES ist Teil der Open Graphics Library (OpenGL®) – Spezifikation für Mobilgeräte wie Android. Informationen zum Konfigurieren von OpenGL ES für Ihr Spiel.
Wenn du Vulkan für dein Spiel verwendest, lies die Erste Schritte mit Vulkan .
Vorbereitung
Falls noch nicht geschehen, ein GameActivity-Objekt eingerichtet: Android-Projekt
OpenGL ES-Variablen einrichten
Sie benötigen ein Display, surface, Kontext und config zum Rendern des Spiels. Fügen Sie den folgende OpenGL ES-Variablen in die Headerdatei Ihrer Spiel-Engine ein:
class NativeEngine { //... private: EGLDisplay mEglDisplay; EGLSurface mEglSurface; EGLContext mEglContext; EGLConfig mEglConfig; bool mHasFocus, mIsVisible, mHasWindow; bool mHasGLObjects; bool mIsFirstFrame; int mSurfWidth, mSurfHeight; }
Initialisieren Sie im Konstruktor für Ihre Spiel-Engine die Standardwerte für die Variablen.
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; }
Initialisieren Sie die Anzeige, um sie zu rendern.
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; }
Die Oberfläche kann ein von EGL zugewiesener Zwischenspeicher außerhalb des Bildschirms oder ein Fenster, das vom Android-Betriebssystem zugewiesen wurde. Initialisieren Sie diese Oberfläche:
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; }
Initialisieren Sie den Renderingkontext. In diesem Beispiel wird ein OpenGL ES 2.0-Kontext:
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; }
Konfigurieren Sie Ihre OpenGL ES-Einstellungen vor dem Zeichnen. Dieses Beispiel wird ausgeführt um am Anfang jedes Frames. Es ermöglicht Tiefentests, legt die klare Farbe auf Schwarz und löscht die Farb- und Tiefenpuffer.
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); }
Mit der Spielschleife rendern
Die Spielschleife rendert einen Frame und wird so lange wiederholt, bis der Nutzer die App beendet. Zwischen den Frames kann Ihr Spiel:
Verarbeitungsereignisse wie Eingabe, Audioausgabe und Netzwerkereignisse.
Spiellogik und Benutzeroberfläche aktualisieren
Stellt einen Frame auf dem Display dar.
Zum Rendern eines Frames für den Bildschirm wird die Methode
DoFrame
aufgerufen. für unbegrenzte Zeit in der Spielschleife: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(); } } }
Fragen Sie in der Methode
DoFrame
die aktuellen Oberflächendimensionen ab, fordern SieSceneManager
, um einen Frame zu rendern, und tauschen die Displayzwischenspeicher aus.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()); } }
Szenen und Objekte rendern
In der Spielschleife wird eine Hierarchie sichtbarer Szenen und Objekte verarbeitet, die gerendert werden sollen. Im Endless Tunnel-Beispiel erfasst ein
SceneManager
mehrere Szenen, mit jeweils nur einer Szene. In diesem Beispiel ist die aktuelle Szene gerendert:void SceneManager::DoFrame() { if (mSceneToInstall) { InstallScene(mSceneToInstall); mSceneToInstall = NULL; } if (mHasGraphics && mCurScene) { mCurScene->DoFrame(); } }
Je nach Spiel kann eine Szene Hintergrund, Text, Sprites und Spielobjekte. Rendere sie in der für dein Spiel geeigneten Reihenfolge. Dieses Beispiel Rendert Hintergrund, Text und Widgets:
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); }
Ressourcen
Hier findest du weitere Informationen zu OpenGL ES und Vulkan:
OpenGL ES: Bilder und Grafiken in Android
OpenGL ES Übersicht in Android Source.
Vulkan: Erste Schritte im NDK
Vulkan – Übersicht in Android Source.
Android-Spielschleifen verstehen – Tempo lernen Frames, Puffer in die Warteschlange ein, verarbeiten VSYNC-Callbacks und verwalten Threads.