بسم الله الرحمن الرحیم
اتاق کار شماره 04 برنامه نویسی Win32
حلقه RealTime
مقدمه: در این اتاق کار می خواهیم یک حلقه پیام به شکل Real Time را بررسی کنیم. ما به بررسی یک تابع با نام PeekMessage و به بررسی تفاوت آن با تابع GetMessage می پردازیم.
به طور کلی استفاده از تابع GetMessage اشتباه نیست امّا این تابع نمی تواند یک نتیجه بسیار خوب را برای بازی ها و صحنه های شبیه ساز تعاملی را بدهد. پس بهترین راه استفاده از PeekMessage است که در این اتاق کار چگونگی کار با این تابع را آموزش می دهیم.
ساختار حلقه GetMessage
در اتاق کار قبلی ما با استفاده از تابع GetMessage یک نمونه پنجره کاربردی را ایجاد کردیم. ما از تابع GetMessage و دو تابع دیگر برای ساختن یک حلقه که در ارسال پیام های تمام پنجره ها به کار می رفت. البته ما نمی خواهیم دوباره درباره اتاق کار قبلی صحبت کنیم پس اگر کمی برایتان این موضوعات مبهم بود حتماً اتاق کارهای قبلی را یک بار دیگر با دقت بخوانید. در نمودار زیر به شما نحوه کار حلقه رویدادها را که نوشتیم نشان می دهیم.
یکبار که ما یک پنجره را ایجاد می کنیم ما آنرا در یک حلقه رویداد قرار می دهیم. جایی که ما تابع GetMessage را می بینیم. GetMessage برای یک پیام صبر می کند و آن را می گیرد و به مرحله بعد می دهد. TranslateMessage هم کاملاً منطقی و روشن برای برنامه نویسی ویندوز است. زیرا به شکل طبیعی با برنامه کاربردی تحت ویندوز صحبت می کند. درست مثل همین نرم افزار Word که تا شما حرکتی نکنید آن هم کاری نمی کند و منتظر حرکتتان می شود.
هرچند ما به این کارها نیازی نداریم. ما تنها به 36 تصویر سه بعدی به شکل تمام رندرشده در هر ثانیه نیاز داریم که در صحنه گذاشته شود بدون هیچ تاخیری. و ما همچنین باید برای رویارویی با یک مشکل آماده شویم. اگر ویندوز برای هر پیام بخواهد صبر کند به طور قطع ما نمی توانیم 30 تا در هر ثانیه بفرستیم.
تابع جدید PeekMessage
ما برای حل این مشکل به جای استفاده از تابع GetMessage از یک تابع جدید با نام PeekMessage استفاده می کنیم. این تابع در اصل مشابه تابع قبلی می باشد. امّا یک تفاوت خیلی مهم دارد و آن تفاوت این است که نمی تواند برای هر چیزی صبر کند. PekkMessage فقط به صف پیامها نگاهی می اندازد و بررسی می کند و اگر پیامی بود اجرا می کند و اگر نبود برنامه می تواند ادامه پیدا کند. این برای چیزی که ما می خواهیم خیلی کاربردی است یک نگاهی به نمودار زیر بیاندازید تا بیشتر متوجه بشوید البته حین نگاه کردن نیم نگاهی هم برای مقایسه به نمودار بالا بیاندازید مطمئناً فرق این دو تابع را متوجه خواهید شد.
از شروع بهتر است نگاهی کوچک و ساده به ساختار تابع PeekMessage بیاندازیم.
| :كد |
BOOL PeekMessage(LPMSG lpMsg,
HWND hWnd,
UINT wMsgFilterMin,
UINT wMsgFilterMax,
UINT wRemoveMsg);
|
چهار پارامتر اوّلیه این تابع مسلماً برای شما آشنا است. این چهار پارامتر در تابع GetMessage هم مورد استفاده قرار می گرفت. امّا پنجمین پارامتر wRemoveMsg جدید است.
در هر صورت پیامهای دریافت شده از صف رویدادها یا متوقف شده یا انجام می شود. ما می توانیم PM_REMOVE یا PM_NOREMOVE را بگذاریم. اوّلین پیامی که از صف رویدادها برای خواندن خارج می شود در آن ثانیه یک پیام برای بازیابی بعد آماده می شود. ما می توانیم در اینجا از مقدار PM_REMOVE استفاده کنیم.
خوب حالا این سوال مطرح می شود که ما چگونه اینها را در برنامه اجرا کنیم. در حلقه اصلی آخرین برنامه ای که نوشتیم باید ما آن را تغییر می دهیم و این بار از PeekMeesage می باشد.
| :كد |
// Enter the infinite message loop
while(TRUE)
{
// Check to see if any messages are waiting in the queue
while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
// Translate the message and dispatch it to WindowProc()
TranslateMessage(&msg);
DispatchMessage(&msg);
}
// If the message is WM_QUIT, exit the while loop
if(msg.message == WM_QUIT)
break;
// Run game code here
// ...
// ...
}
|
اکنون برنامه ما می تواند بارها اجرا شود بدون هیچ ترسی از ویندوز و پیامهای خسته کننده اش. خوب بریم و یک نگاه سریع به تغییراتمان بکنیم.
به طور طبیعی یک حلقه نامحدود می سازد. هنگام خروج از بازی می توانیم از این حلقه خارج شویم.
| :كد |
while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
|
این را استفاده کردیم چون نمی خواهیم برای پیام های ویندوز صبر کنیم امّا یک زیرچشمی هم به پیام های ویندوز داریم. ما در اینجا می خواهیم از PeekMessage استفاده کنیم. PeekMessage مقدار TRUE را درصورتی که پیامی دریافت کند برمی گرداند و FALSE را اگر دریافت نکند و همچنین محتویات حلقه while را اجرا می کند که کدهای بازی ما در آن قرار دارد.
| :كد |
if(msg.message == WM_QUIT)
|
این خط کد این معنی را دارد که اگر پیام ما WM_QUIT بود زمانی است که ما باید از حلقه پنجره مان خارج شویم. به یاد بیاورید که ما همچنین چیزی را در GetMessage نداشتیم زیرا در آن تابع ما مقدار 0 را در هنگام بسته شدن برمی گردانیم. امّا در اینجا باید از حلقه خارج شویم و یک مقدار اصول کار فرق دارد.
اینجا کدهای جدیدمان قرار دارد.
| :كد |
// include the basic windows header file
#include <windows.h>
#include <windowsx.h>
// the WindowProc function prototype
LRESULT CALLBACK WindowProc(HWND hWnd,
UINT message,
WPARAM wParam,
LPARAM lParam);
// the entry point for any Windows program
int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
// the handle for the window, filled by a function
HWND hWnd;
// this struct holds information for the window class
WNDCLASSEX wc;
// clear out the window class for use
ZeroMemory(&wc, sizeof(WNDCLASSEX));
// fill in the struct with the needed information
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
wc.lpszClassName = L"WindowClass1";
// register the window class
RegisterClassEx(&wc);
// create the window and use the result as the handle
hWnd = CreateWindowEx(NULL,
L"WindowClass1", // name of the window class
L"Our First Windowed Program", // title of the window
WS_OVERLAPPEDWINDOW, // window style
300, // x-position of the window
300, // y-position of the window
500, // width of the window
400, // height of the window
NULL, // we have no parent window, NULL
NULL, // we aren't using menus, NULL
hInstance, // application handle
NULL); // used with multiple windows, NULL
// display the window on the screen
ShowWindow(hWnd, nCmdShow);
// enter the main loop:
// this struct holds Windows event messages
MSG msg;
// Enter the infinite message loop
while(TRUE)
{
// Check to see if any messages are waiting in the queue
while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
// translate keystroke messages into the right format
TranslateMessage(&msg);
// send the message to the WindowProc function
DispatchMessage(&msg);
}
// If the message is WM_QUIT, exit the while loop
if(msg.message == WM_QUIT)
break;
// Run game code here
// ...
// ...
}
// return this part of the WM_QUIT message to Windows
return msg.wParam;
}
// this is the main message handler for the program
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
// sort through and find what code to run for the message given
switch(message)
{
// this message is read when the window is closed
case WM_DESTROY:
{
// close the application entirely
PostQuitMessage(0);
return 0;
} break;
}
// Handle any messages the switch statement didn't
return DefWindowProc (hWnd, message, wParam, lParam);
}
|
هنگامی که شما برنامه تان را اجرا می کنیم می بینید که در خروجی این دو تابع GetMessage و PeekMessage هیچ تفاوت چندانی با هم ندارد. در حقیقت شما تفاوت این دو را در برنامه نویسی بازی مشاهده خواهید کرد.
خوب من خیلی خوشحالم که به لطف خداوند توانستم در چهار اتاق کار برنامه نویسی لازم تحت ویندوز برای ساخت بازی را به شکل کامل به شما یاد دادم و این اتاق کار پایان برنامه نویسی تحت ویندوز برای ساخت بازی می باشد. درست در ظاهر کار ممکن است یک مقدار پیچیده باشد امّا مطمئن باشید با کمی تمرین تمام این توابع و کارکرد آنها را یاد خواهید گرفت. اگر تا اکنون تمام اتاق کارها را تمرین و یاد گرفته اید کاملاً برای یادگیری DirectX آماده اید پس من به شما پیشنهاد می کنم بعد از اتمام این چهار اتاق کار آموزش های ما در اتاق کارهای DirectX را پی گیری کنید.
نویسنده این اتاق کارها خادم همیشگی شما جویندگان علم و دانش محمد غضنفری می باشد.
_________________
هدف ما پیشرفت صنعت بازی های رایانه ای در ایران است.
امام باقر (علیه السلام): دانشمندی که از علمش استفاده شود از هفتاد هزار عابد بهتر است.