게임에 객체와 스프라이트를 그리려면 디스플레이, 표면 및 컨텍스트 변수를 구성하고 게임 루프에서 렌더링을 설정하고 각 장면과 객체를 그려야 합니다.
C 또는 C++ 게임의 화면에 이미지를 그리는 방법은 OpenGL ES 또는 Vulkan, 두 가지가 있습니다.
- OpenGL ES는 Android와 같은 휴대기기용 Open Graphics Library(OpenGL®) 사양의 일부입니다. 이 주제에서 게임의 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 콜백 처리, 스레드 관리 방법을 알아봅니다.