Para desenhar objetos e sprites no jogo, você precisará configurar as variáveis de tela, superfície e contexto, configurar a renderização no loop do jogo e desenhar cada cena e objeto.
Há duas maneiras de desenhar imagens na tela para um jogo em C ou C++, ou seja, com o OpenGL ES ou o Vulkan.
O OpenGL ES faz parte da especificação da Biblioteca Open Graphics (OpenGL®) (link em inglês) destinada a dispositivos móveis, como o Android. Saiba como configurar o OpenGL ES para seu jogo neste tópico.
Se você usa o Vulkan no seu jogo, leia a Introdução à Vulkan guia.
Antes de começar
Configure um objeto GameActivity no seu projeto do Android, caso ainda não tenha feito isso.
Configurar variáveis do OpenGL ES
Você precisará de tela, superfície, contexto e configuração para renderizar o jogo. Adicione as seguintes variáveis do OpenGL ES ao arquivo principal do seu mecanismo de jogo:
class NativeEngine { //... private: EGLDisplay mEglDisplay; EGLSurface mEglSurface; EGLContext mEglContext; EGLConfig mEglConfig; bool mHasFocus, mIsVisible, mHasWindow; bool mHasGLObjects; bool mIsFirstFrame; int mSurfWidth, mSurfHeight; }
No construtor do mecanismo do jogo, inicialize os valores padrão das variáveis.
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; }
Inicialize a tela para renderizar.
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; }
A superfície pode ser um buffer fora da tela (pbuffer) alocado pelo EGL ou uma janela alocada pelo SO Android. Inicialize esta superfície:
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; }
Inicialize o contexto de renderização. Este exemplo cria um contexto do 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; }
Defina as configurações do OpenGL ES antes de desenhar. Este exemplo é executado no início de cada frame. Ele ativa testes de profundidade, define a cor clara como preto e limpa os buffers de cor e profundidade.
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); }
Renderizar com o loop de jogo
O loop de jogo renderiza um frame e se repete indefinidamente até o usuário sair. Entre os frames, o jogo pode:
processar eventos, como eventos de entrada, saída de áudio e rede;
atualizar a lógica do jogo e a interface do usuário;
renderizar um frame para a tela.
Para renderizar um frame para a tela, o método
DoFrame
é chamado indefinidamente no loop de jogo: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(); } } }
No método
DoFrame
, consulte as dimensões de superfície atuais, soliciteSceneManager
para renderizar um frame e troque os buffers de exibição.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()); } }
Renderizar cenas e objetos
O loop de jogo processa uma hierarquia de cenas e objetos visíveis para renderização. No exemplo Endless Tunnel, um
SceneManager
monitora várias cenas, com apenas uma cena ativa por vez. Neste exemplo, a cena atual é renderizada:void SceneManager::DoFrame() { if (mSceneToInstall) { InstallScene(mSceneToInstall); mSceneToInstall = NULL; } if (mHasGraphics && mCurScene) { mCurScene->DoFrame(); } }
Dependendo do jogo, uma cena pode conter segundo plano, texto, sprites e objetos de jogo. Renderize-os na ordem adequada para o jogo. Este exemplo renderiza o segundo plano, o texto e os 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); }
Recursos
Leia o seguinte para saber mais sobre o OpenGL ES e o Vulkan:
OpenGL ES: imagens e gráficos no Android.
OpenGL ES: visão geral no Android Source.
Vulkan: primeiros passos no NDK.
Vulkan: visão geral no Android Source.
Entenda os loops de jogo do Android: saiba como ajustar o ritmo de frames, enfileirar buffers, processar callbacks VSYNC e gerenciar linhas de execução.