Чтобы рисовать объекты и спрайты в вашей игре, вам необходимо настроить переменные отображения, поверхности и контекста, настроить рендеринг в игровом цикле и нарисовать каждую сцену и объект.
Существует два способа рисования изображений на экране для игры C или C++, а именно с помощью OpenGL ES или Vulkan .
OpenGL ES является частью спецификации Open Graphics Library (OpenGL®), предназначенной для мобильных устройств, таких как Android. Узнайте, как настроить OpenGL ES для вашей игры, в этом разделе.
Если вы используете Vulkan для своей игры, прочтите руководство «Начало работы с Vulkan» .
Прежде чем начать
Если вы еще этого не сделали, настройте объект GameActivity в своем проекте Android.
Настройка переменных OpenGL ES
Для рендеринга игры вам понадобятся display , Surface , context и config . Добавьте следующие переменные 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;
}Поверхность может представлять собой внеэкранный буфер (pbuffer), выделенный EGL, или окно, выделенное ОС Android. Инициализируйте эту поверхность:
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());
}
}
Рендеринг сцен и объектов
Игровой цикл обрабатывает иерархию видимых сцен и объектов для рендеринга. В примере с «Бесконечным туннелем»
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.
Вулкан — Начало работы в NDK.
Vulkan — обзор в исходном коде Android.
Поймите игровые циклы Android — научитесь управлять кадрами, буферами очередей, обрабатывать обратные вызовы VSYNC и управлять потоками.