Để vẽ các đối tượng và sprite trong trò chơi, bạn cần định cấu hình các biến màn hình, bề mặt (surface) và ngữ cảnh, thiết lập hoạt động kết xuất trong vòng lặp trò chơi, vẽ từng cảnh và đối tượng.
Có hai cách để vẽ hình ảnh lên màn hình cho trò chơi C hoặc C++, đó là sử dụng OpenGL ES hoặc Vulkan.
OpenGL ES là một phần tính năng kỹ thuật của Open Graphics Library (OpenGL®) dành cho các thiết bị di động như Android. Tìm hiểu cách định cấu hình OpenGL ES cho trò chơi trong chủ đề này.
Nếu bạn sử dụng Vulkan cho trò chơi, hãy đọc hướng dẫn Bắt đầu sử dụng Vulkan.
Trước khi bắt đầu
Thiết lập đối tượng GameActivity trong dự án Android, nếu bạn chưa làm việc này.
Thiết lập biến OpenGL ES
Bạn cần có một màn hình, bề mặt, ngữ cảnh và cấu hình để kết xuất trò chơi. Thêm các biến OpenGL ES sau vào tệp tiêu đề của công cụ phát triển trò chơi:
class NativeEngine { //... private: EGLDisplay mEglDisplay; EGLSurface mEglSurface; EGLContext mEglContext; EGLConfig mEglConfig; bool mHasFocus, mIsVisible, mHasWindow; bool mHasGLObjects; bool mIsFirstFrame; int mSurfWidth, mSurfHeight; }
Trong hàm khởi tạo cho công cụ phát triển trò chơi, hãy khởi động các giá trị mặc định cho biến.
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; }
Khởi động màn hình để kết xuất.
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; }
Bề mặt ở đây có thể là vùng đệm ngoài màn hình (pbuffer) do EGL phân bổ, hoặc một cửa sổ do Hệ điều hành Android phân bổ. Khởi động bề mặt này:
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; }
Khởi động ngữ cảnh kết xuất. Ví dụ này sẽ tạo một ngữ cảnh 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; }
Định cấu hình chế độ cài đặt OpenGL ES trước khi vẽ. Ví dụ này được thực thi ở đầu mỗi khung. Việc này cho phép kiểm thử độ sâu, đặt màu trong suốt thành màu đen, cũng như xoá vùng đệm màu và độ sâu.
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); }
Kết xuất trong vòng lặp trò chơi
Vòng lặp trò chơi kết xuất một khung và lặp lại vô hạn cho đến khi người dùng thoát. Giữa các khung hình, trò chơi của bạn có thể:
Xử lý các sự kiện như sự kiện nhập, xuất âm thanh và kết nối mạng.
Cập nhật logic và giao diện người dùng của trò chơi.
Kết xuất một khung vào màn hình.
Để kết xuất một khung vào màn hình, phương thức
DoFrame
được gọi vô thời hạn trong vòng lặp trò chơi: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(); } } }
Trong phương thức
DoFrame
, hãy truy vấn các chiều kích thước của bề mặt hiện tại, yêu cầuSceneManager
kết xuất một khung và hoán đổi vùng đệm hiển thị.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()); } }
Kết xuất cảnh và đối tượng
Vòng lặp trò chơi xử lý một hệ thống phân cấp các cảnh và đối tượng hiển thị cần kết xuất. Trong ví dụ về Endless Tunnel,
SceneManager
theo dõi nhiều cảnh, mỗi lần chỉ có một cảnh hoạt động. Trong ví dụ này, cảnh hiện tại sẽ được kết xuất:void SceneManager::DoFrame() { if (mSceneToInstall) { InstallScene(mSceneToInstall); mSceneToInstall = NULL; } if (mHasGraphics && mCurScene) { mCurScene->DoFrame(); } }
Tuỳ thuộc vào trò chơi, một cảnh có thể chứa nền, văn bản, sprite và đối tượng trò chơi. Hãy kết xuất chúng theo thứ tự phù hợp với trò chơi. Ví dụ này kết xuất nền, văn bản và tiện ích:
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); }
Tài nguyên
Đọc các nội dung dưới đây để biết thêm thông tin về OpenGL ES và Vulkan:
OpenGL ES – Hình ảnh và đồ hoạ trong Android.
OpenGL ES – Tổng quan trong Nguồn Android.
Vulkan – Bắt đầu trên NDK.
Vulkan – Tổng quan trong Nguồn Android.
Tìm hiểu vòng lặp trò chơi trên Android – tìm hiểu cách điều chỉnh tốc độ khung hình, xếp hàng đợi cho vùng đệm, xử lý lệnh gọi lại VSYNC và quản lý luồng.