如需在游戏中绘制对象和精灵,您需要配置显示变量、Surface 变量和上下文变量,在游戏循环中设置渲染,然后绘制每个场景和对象。
对于 C 或 C++ 游戏,您可以通过两种方式将图像绘制到屏幕上,即,使用 OpenGL ES 或 Vulkan。
OpenGL ES 是适用于 Android 等移动设备的 Open Graphics Library (OpenGL®) 规范的一部分。本主题将介绍如何为您的游戏配置 OpenGL ES。
如果您在游戏中使用 Vulkan,请阅读 Vulkan 使用入门指南。
准备工作
如果您尚未在 Android 项目中设置 GameActivity 对象,请执行此操作。
设置 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;
}Surface 可以是由 EGL 分配的离屏缓冲区 (pbuffer),也可以是由 Android 操作系统分配的窗口。初始化此 Surface:
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
方法会查询当前的 Surface 尺寸,请求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 回调以及管理线程。