برای ترسیم اشیا و اسپرایت ها در بازی خود، باید نمایشگر، سطح و متغیرهای زمینه را پیکربندی کنید، رندرینگ را در حلقه بازی خود تنظیم کنید و هر صحنه و شی را ترسیم کنید.
دو راه برای کشیدن تصاویر روی صفحه نمایش برای یک بازی C یا C++ وجود دارد، یعنی با OpenGL ES یا Vulkan .
OpenGL ES بخشی از مشخصات Open Graphics Library (OpenGL®) است که برای دستگاه های تلفن همراه مانند Android در نظر گرفته شده است. در این مبحث با نحوه پیکربندی OpenGL ES برای بازی خود آشنا شوید.
اگر از Vulkan برای بازی خود استفاده می کنید، راهنمای شروع با Vulkan را بخوانید.
قبل از اینکه شروع کنید
اگر قبلاً این کار را نکردهاید، یک شی GameActivity را در پروژه Android خود تنظیم کنید .
متغیرهای 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; }
سطح می تواند یک بافر خارج از صفحه (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 - تصاویر و گرافیک در اندروید.
OpenGL ES - نمای کلی در منبع اندروید.
Vulkan - شروع به کار در NDK.
Vulkan - نمای کلی در منبع اندروید.
حلقههای بازی اندروید را درک کنید - سرعت دادن به فریمها، بافرهای صف، رسیدگی به تماسهای VSYNC و مدیریت رشتهها را بیاموزید.