Sử dụng bộ sưu tập để sắp xếp ngăn nắp các trang
Lưu và phân loại nội dung dựa trên lựa chọn ưu tiên của bạn.
Để 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.
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:
boolNativeEngine::InitDisplay(){if(mEglDisplay!=EGL_NO_DISPLAY){returntrue;}mEglDisplay=eglGetDisplay(EGL_DEFAULT_DISPLAY);if(EGL_FALSE==eglInitialize(mEglDisplay,0,0)){LOGE("NativeEngine: failed to init display, error %d",eglGetError());returnfalse;}returntrue;}
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:
boolNativeEngine::InitSurface(){ASSERT(mEglDisplay!=EGL_NO_DISPLAY);if(mEglSurface!=EGL_NO_SURFACE){returntrue;}EGLintnumConfigs;constEGLintattribs[]={EGL_RENDERABLE_TYPE,EGL_OPENGL_ES2_BIT,// request OpenGL ES 2.0EGL_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());returnfalse;}returntrue;}
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:
boolNativeEngine::InitContext(){ASSERT(mEglDisplay!=EGL_NO_DISPLAY);if(mEglContext!=EGL_NO_CONTEXT){returntrue;}// OpenGL ES 2.0EGLintattribList[]={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());returnfalse;}returntrue;}
Đị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.
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:
voidNativeEngine::GameLoop(){// Loop indefinitely.while(1){intevents;structandroid_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ầu SceneManager kết xuất một khung và hoán đổi vùng đệm hiển thị.
voidNativeEngine::DoFrame(){...// Query the current surface dimension.intwidth,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:
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:
voidUiScene::DoFrame(){// clear screenglClearColor(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 elseif(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(inti=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:
Nội dung và mã mẫu trên trang này phải tuân thủ các giấy phép như mô tả trong phần Giấy phép nội dung. Java và OpenJDK là nhãn hiệu hoặc nhãn hiệu đã đăng ký của Oracle và/hoặc đơn vị liên kết của Oracle.
Cập nhật lần gần đây nhất: 2025-07-27 UTC.
[[["Dễ hiểu","easyToUnderstand","thumb-up"],["Giúp tôi giải quyết được vấn đề","solvedMyProblem","thumb-up"],["Khác","otherUp","thumb-up"]],[["Thiếu thông tin tôi cần","missingTheInformationINeed","thumb-down"],["Quá phức tạp/quá nhiều bước","tooComplicatedTooManySteps","thumb-down"],["Đã lỗi thời","outOfDate","thumb-down"],["Vấn đề về bản dịch","translationIssue","thumb-down"],["Vấn đề về mẫu/mã","samplesCodeIssue","thumb-down"],["Khác","otherDown","thumb-down"]],["Cập nhật lần gần đây nhất: 2025-07-27 UTC."],[],[],null,["# Configure graphics with OpenGL ES\n\nTo draw objects and sprites in your game, you will need to configure the\ndisplay, surface and context variables, set up rendering in your game loop, and\ndraw each scene and object.\n\nThere are two ways to draw images to the screen for a C or C++ game, namely with\n[OpenGL ES](/develop/ui/views/graphics/opengl/about-opengl), or\n[Vulkan](/ndk/guides/graphics/getting-started).\n\n- [OpenGL ES](/develop/ui/views/graphics/opengl/about-opengl) is part of the [Open Graphics\n Library (OpenGL®)](https://www.khronos.org/opengles/) specification\n intended for mobile devices such as Android. Learn how to configure OpenGL ES\n for your game in this topic.\n\n- If you use Vulkan for your game, read the\n [Getting started with Vulkan](/ndk/guides/graphics/getting-started)\n guide.\n\n| **Note:** The code in this topic is based on the [Endless Tunnel](https://github.com/android/ndk-samples/tree/master/endless-tunnel) sample, where details may differ for your game. Understand and adapt these concepts for your specific use case.\n\nBefore you get started\n----------------------\n\nIf you haven't already done so,\n[set up a GameActivity object](/games/agdk/game-activity) in your\nAndroid project.\n\nSet up OpenGL ES variables\n--------------------------\n\n1. You will need a [display](/reference/android/opengl/EGLDisplay),\n [surface](/reference/android/opengl/EGLSurface),\n [context](/reference/android/opengl/EGLContext), and\n [config](/reference/android/opengl/EGLConfig) to render your game. Add the\n following OpenGL ES variables to your game engine's header file:\n\n class NativeEngine {\n //...\n private:\n EGLDisplay mEglDisplay;\n EGLSurface mEglSurface;\n EGLContext mEglContext;\n EGLConfig mEglConfig;\n\n bool mHasFocus, mIsVisible, mHasWindow;\n bool mHasGLObjects;\n bool mIsFirstFrame;\n\n int mSurfWidth, mSurfHeight;\n }\n\n2. In the constructor for your game engine, initialize the default values for\n the variables.\n\n NativeEngine::NativeEngine(struct android_app *app) {\n //...\n mEglDisplay = EGL_NO_DISPLAY;\n mEglSurface = EGL_NO_SURFACE;\n mEglContext = EGL_NO_CONTEXT;\n mEglConfig = 0;\n\n mHasFocus = mIsVisible = mHasWindow = false;\n mHasGLObjects = false;\n mIsFirstFrame = true;\n\n mSurfWidth = mSurfHeight = 0;\n }\n\n3. Initialize the display to render.\n\n bool NativeEngine::InitDisplay() {\n if (mEglDisplay != EGL_NO_DISPLAY) {\n return true;\n }\n\n mEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);\n if (EGL_FALSE == eglInitialize(mEglDisplay, 0, 0)) {\n LOGE(\"NativeEngine: failed to init display, error %d\", eglGetError());\n return false;\n }\n return true;\n }\n\n4. The surface can be an off-screen buffer (pbuffer) allocated by EGL, or a\n window allocated by the Android OS. Initialize this surface:\n\n bool NativeEngine::InitSurface() {\n ASSERT(mEglDisplay != EGL_NO_DISPLAY);\n if (mEglSurface != EGL_NO_SURFACE) {\n return true;\n }\n\n EGLint numConfigs;\n const EGLint attribs[] = {\n EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, // request OpenGL ES 2.0\n EGL_SURFACE_TYPE, EGL_WINDOW_BIT,\n EGL_BLUE_SIZE, 8,\n EGL_GREEN_SIZE, 8,\n EGL_RED_SIZE, 8,\n EGL_DEPTH_SIZE, 16,\n EGL_NONE\n };\n\n // Pick the first EGLConfig that matches.\n eglChooseConfig(mEglDisplay, attribs, &mEglConfig, 1, &numConfigs);\n mEglSurface = eglCreateWindowSurface(mEglDisplay, mEglConfig, mApp-\u003ewindow,\n NULL);\n if (mEglSurface == EGL_NO_SURFACE) {\n LOGE(\"Failed to create EGL surface, EGL error %d\", eglGetError());\n return false;\n }\n return true;\n }\n\n5. Initialize the rendering context. This example creates an\n [OpenGL ES 2.0](/reference/android/opengl/GLES20) context:\n\n bool NativeEngine::InitContext() {\n ASSERT(mEglDisplay != EGL_NO_DISPLAY);\n if (mEglContext != EGL_NO_CONTEXT) {\n return true;\n }\n\n // OpenGL ES 2.0\n EGLint attribList[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };\n mEglContext = eglCreateContext(mEglDisplay, mEglConfig, NULL, attribList);\n if (mEglContext == EGL_NO_CONTEXT) {\n LOGE(\"Failed to create EGL context, EGL error %d\", eglGetError());\n return false;\n }\n return true;\n }\n\n6. Configure your OpenGL ES settings before drawing. This example is executed at\n the beginning of every frame. It enables depth testing, sets the clear color to\n black, and clears the color and depth buffers.\n\n void NativeEngine::ConfigureOpenGL() {\n glClearColor(0.0f, 0.0f, 0.0f, 1.0f);\n glEnable(GL_DEPTH_TEST);\n glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);\n }\n\nRender with the game loop\n-------------------------\n\n1. The game loop renders a frame and repeats indefinitely until the user quits.\n Between frames, your game may:\n\n - [Process events](/games/agdk/game-activity/get-started#handle-events) such as\n input, [audio output](/games/sdk/oboe), and networking events.\n\n - Update the game logic and user interface.\n\n - Render a frame to the display.\n\n To render a frame to the display, the `DoFrame` method is called\n indefinitely in the game loop: \n\n void NativeEngine::GameLoop() {\n // Loop indefinitely.\n while (1) {\n int events;\n struct android_poll_source* source;\n\n // If not animating, block until we get an event.\n while ((ALooper_pollAll(IsAnimating() ? 0 : -1, NULL, &events,\n (void **) &source)) \u003e= 0) {\n // Process events.\n ...\n }\n\n // Render a frame.\n if (IsAnimating()) {\n DoFrame();\n }\n }\n }\n\n2. In the `DoFrame` method, query the current surface dimensions, request\n `SceneManager` to render a frame, and swap the display buffers.\n\n void NativeEngine::DoFrame() {\n ...\n // Query the current surface dimension.\n int width, height;\n eglQuerySurface(mEglDisplay, mEglSurface, EGL_WIDTH, &width);\n eglQuerySurface(mEglDisplay, mEglSurface, EGL_HEIGHT, &height);\n\n // Handle dimension changes.\n SceneManager *mgr = SceneManager::GetInstance();\n if (width != mSurfWidth || height != mSurfHeight) {\n mSurfWidth = width;\n mSurfHeight = height;\n mgr-\u003eSetScreenSize(mSurfWidth, mSurfHeight);\n glViewport(0, 0, mSurfWidth, mSurfHeight);\n }\n ...\n // Render scenes and objects.\n mgr-\u003eDoFrame();\n\n // Swap buffers.\n if (EGL_FALSE == eglSwapBuffers(mEglDisplay, mEglSurface)) {\n HandleEglError(eglGetError());\n }\n }\n\nRender scenes and objects\n-------------------------\n\n1. The game loop processes a hierarchy of visible scenes and objects to render.\n In the Endless Tunnel example, a `SceneManager` keeps track of multiple scenes,\n with only one scene active at a time. In this example, the current scene is\n rendered:\n\n void SceneManager::DoFrame() {\n if (mSceneToInstall) {\n InstallScene(mSceneToInstall);\n mSceneToInstall = NULL;\n }\n\n if (mHasGraphics && mCurScene) {\n mCurScene-\u003eDoFrame();\n }\n }\n\n2. Depending on your game, a scene may contain background, text, sprites and\n game objects. Render them in the order suitable for your game. This example\n renders the background, text, and widgets:\n\n void UiScene::DoFrame() {\n // clear screen\n glClearColor(0.0f, 0.0f, 0.0f, 1.0f);\n glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);\n glDisable(GL_DEPTH_TEST);\n\n RenderBackground();\n\n // Render the \"Please Wait\" sign and do nothing else\n if (mWaitScreen) {\n SceneManager *mgr = SceneManager::GetInstance();\n mTextRenderer-\u003eSetFontScale(WAIT_SIGN_SCALE);\n mTextRenderer-\u003eSetColor(1.0f, 1.0f, 1.0f);\n mTextRenderer-\u003eRenderText(S_PLEASE_WAIT, mgr-\u003eGetScreenAspect() * 0.5f,\n 0.5f);\n glEnable(GL_DEPTH_TEST);\n return;\n }\n\n // Render all the widgets.\n for (int i = 0; i \u003c mWidgetCount; ++i) {\n mWidgets[i]-\u003eRender(mTrivialShader, mTextRenderer, mShapeRenderer,\n (mFocusWidget \u003c 0) ? UiWidget::FOCUS_NOT_APPLICABLE :\n (mFocusWidget == i) ? UiWidget::FOCUS_YES : UiWidget::FOCUS_NO,tf);\n }\n glEnable(GL_DEPTH_TEST);\n }\n\nResources\n---------\n\nRead the following for more information about OpenGL ES and Vulkan:\n\n- [OpenGL ES](/develop/ui/views/graphics/opengl/about-opengl) - Images and graphics in Android.\n\n- [OpenGL ES](https://source.android.com/devices/graphics/arch-egl-opengl) -\n Overview in Android Source.\n\n- [Vulkan](/ndk/guides/graphics/getting-started) - Getting started in NDK.\n\n- [Vulkan](https://source.android.com/devices/graphics/arch-vulkan) - Overview\n in Android Source.\n\n- [Understand Android game loops](/games/develop/gameloops) - learn to pace\n frames, queue buffers, handle VSYNC callbacks, and manage threads."]]