การเขียนโปรแกรมส่วนที่จำเป็นในการสร้างเกมบน Windows



การเรียก include ตัว Library ของ DirectX

#include <d3dx9.h>
#include <dinput.h>
#include <dinputd.h>
#include <dsound.h>

ที่ด้านบนสุดของโปรแกรม เราต้องเรียก include file ของ DirectX ใน header file เหล่านี้จะประกอบด้วยส่วนต่าง ๆ ของ DirectX นั้น ๆ ที่เราจะสามารถเรียกเข้ามาใช้ได้ เช่น หากเราต้องการเรียกใช้ Direct 3D เราต้องสร้างตัว device ขึ่นมาก่อน

LPDIRECT3DDEVICE9 g_pd3dDevice = NULL;

โดยในตัวอย่าง จะเป็นการสร้างตัวแปร pointer ที่ชื่อไป object แบบ Direct 3D Device ซึ่ง object นี้จะอ้างอิงอยู่ที่ file d3dx9.h เป็นต้น

จะเขียนเกม 1 เกมบน Windows เราต้องมีอะไรบ้าง
ผมเริ่มต้นด้วยส่วนประกอบเหล่านี้เลยนะครับ เพื่อไม่ให้สับสนว่าการจะเขียนเกม ทำไมต้องมีอะไรยุ่งวุ่นวายด้วย ส่วนประกอบเหล่านี้มันจำเป็นต่อการสร้างเกม หรือโปรแกรมทั่ว ๆ ไปบน Windows ครับ และถึงแม้มันจะมีรายละเอียดคล้าย ๆ กัน แต่เราก็ควรจะเข้าใจเบื้องต้นไว้บ้างครับ

Function แรก คือ Winmain
ถ้าเราลองไปดูโปรแกรมตัวอย่างภาษา c++ ทั่วไปจะออกมาคล้าย ๆ กันครับ หน้าตามันจะประมาณนี้ครับ )แต่ใน C เก่า ๆ อาจจะมี function main บ้าง)

int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow )
{
}

ตัว Winmain จะเป็นตัวเริ่มโปรแกรมของเราเลย ทุกอย่างจะเริ่มตรงนี้นะครับ Function นี้จะมี parameter อยู่หลายตัว แต่ในที่นี้จะกล่าวถึงส่วนสำคัญคือ HINSTANCE เจ้าตัวนี้จะเป็นเหมือนตัวเชื่อมตัวระหว่างโปรแกรมของเรากับตัว Window ที่เราจะสร้างขึ้นนะครับ

WNDCLASSEX winClass;
MSG uMsg;
memset(&uMsg,0,sizeof(uMsg));

winClass.lpszClassName = "MY_WINDOWS_CLASS";
winClass.cbSize = sizeof(WNDCLASSEX);
winClass.style = CS_HREDRAW | CS_VREDRAW;
winClass.lpfnWndProc = WindowProc;
winClass.hInstance = hInstance;
winClass.hIcon = LoadIcon(hInstance, (LPCTSTR)IDI_DIRECTX_ICON);
winClass.hIconSm = LoadIcon(hInstance, (LPCTSTR)IDI_DIRECTX_ICON);
winClass.hCursor = LoadCursor(NULL, IDC_ARROW);
winClass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
winClass.lpszMenuName = NULL;
winClass.cbClsExtra = 0;
winClass.cbWndExtra = 0;

ในตัว Winmain นี้เราก็จะสร้าง object Window ขึ้นมา ให้ชื่อว่า winclass ครับ แล้วเราก็กำหนดรายละเอียดของมันลงไปประมาณนี้ครับ ตัวที่สำคัญคือ winClass.hInstance = hInstance; คือจะบอกว่าเมื่อเราสร้าง Window ขึ้นมาแล้ว ให้มันไปเชื่อมกับโปรแกรมของเรา
ต่อมาคือ winClass.lpfnWndProc = WindowProc; เป็นการตั้งค่า pointer function อันนี้เป็นฟังก์ชั่นที่เราจะเรียกใช้ ระบบ message ของ Windows ครับ ระบบ message นี้คือ ระบบที่มีการส่งข้อความไปมาว่ามีการกระทำอะไรกับ window ของเราผ่าน OS Windows บ้าง เช่น การปิด window การ focus window เป็นต้น เมื่อมีการกระทำใด ๆ แล้ว Windows จะส่งข้อความมาบอก โดยเราสามารถรับข้อความนั้นผ่านทาง Function ที่ชื่อ WindowProc ครับ
แต่ไม่ต้องงงครับว่า WindowProc อยู่ที่ไหน เพราะเราจะสร้างขึ้นต่อไปครับ

หลังจากเราสร้างตัวแปร winClass แล้วเราจะมาสร้างตัว window กัน

if( !RegisterClassEx(&winClass) )
return E_FAIL;
WindowCanvasX=1280;
WindowCanvasY=720;
g_hWnd = CreateWindowEx( NULL, "MY_WINDOWS_CLASS",
"game",
WS_POPUP,
CW_USEDEFAULT,
CW_USEDEFAULT,
WindowCanvasX, WindowCanvasY,
NULL, NULL, hInstance, NULL );

if( g_hWnd == NULL ) return E_FAIL;
ShowWindow( g_hWnd, nCmdShow );
UpdateWindow( g_hWnd );

จากตัวอย่างเป็นการสร้าง window ขึ้นมาจากคำสั่ง CreateWindowEx โดยให้ ชื่อว่า game โดย ของ window จะเป็นแบบ POPUP (ถ้าเราเลือกให้เป็นแบบมี title bar จะมีชื่อเขียนว่า game) โดย window นี้จะมีขนาด 1280*720 จุด
มีข้อแนะนำอีกอย่างนะครับ ถ้าเราเพิ่ม style ให้เป็น window แบบมีขอบ ขนาดของมันอาจจะต้องหักส่วนที่เป็น ขอบ และ title bar ไปด้วย ซึ่งทำให้ canvas ที่แท้จริงอาจจะผิดได้ (มีผลทำให้ภาพที่แสดงผลมันเบี้ยว)

จะเห็นว่าเมื่อเราสร้าง window มาแล้ว เราจะใช้ตัวแปรชื่อ g_hWnd มารับเอาไว้ ตัวแปรนี้เราสามารถไปกำหนดใน global ได้

HWND g_hWnd = NULL;

เจ้าตัวนี้เราจะเก็บไว้ใช้อีกที หากมีการเปลี่ยนแปลงขนาดของ window

Loop game
จริง ๆ แล้วในการเขียนเกมทุกแบบจะมีส่วนของ loop game อยู่นะครับ ผมจะยกตัวอย่างที่ผมใช้ให้ดูครับ

while( uMsg.message != WM_QUIT ) {
if( PeekMessage( &uMsg, NULL, 0, 0, PM_REMOVE ) ) {
TranslateMessage( &uMsg );
DispatchMessage( &uMsg );
} else {
g_dCurTime = timeGetTime();
g_fElpasedTime = (float)((g_dCurTime - g_dLastTime) * 0.001);
g_dLastTime = g_dCurTime;
if (g_fElpasedTime>=0.03) {
g_dLastTime = g_dCurTime;
Game_input();
Game_play();
}
Game_render();
}
}
shutDown();
UnregisterClass( "MY_WINDOWS_CLASS", winClass.hInstance );
return uMsg.wParam;

ที่เราเรียกว่า loop game นั้นเพราะว่ามีจะมี loop ที่มันทำงานไม่จบสิ้นอยู่นะครับ ในที่นี้คือ loop while นั้นเอง loop นี้จะออกก็ต่อเมื่อมี message เข้ามาว่ามีการออกจากโปรแกรม โดยมีการตรวจสอบ message นี้อยู่ใน loop ด้วย เมื่อมีการส่ง message ว่า WM_QUIT ก็จะมีการสั่งเคลียร์ข้อมูลต่าง ๆ ต่อไป (ในที่นี้คือ shutDown();)
ใน loop ของเกมนั้นจะมีการจับเวลาอยู่ด้วย อันนี้เป็นเทคนิกในเกมโดยเฉพาะครับ นั่นคือ

  • เกมจำตรวจสอบว่าใน loop นี้ใช้เวลาในการทำงานเท่าไหร่
  • หากน้อยกว่า 0.03 วินาที (จะได้ 30 ครั้งต่อวินาที) ให้ทำการคำนวณเกี่ยวกับเกม 1 ครั้ง (ในที่นี้คือ Game_input(); และ Game_play();)
  • ส่วนของการวาดภาพนั้น (ในที่นี้คือ Game_render(); ) เราจะไม่จับ vertical sync นะครับ (ใครอยากทำแบบจับก็ได้ ไม่ว่ากัน) นั่นคือโปรแกรมจะวาดรูปตลอดเวลา วาดให้ได้ถี่ที่สุดเท่าที่จะทำได้
  • การจับเวลาในแต่ละครั้งจะได้เวลาในแต่ละ loop คือ g_fElpasedTime โดยปกติแล้วถ้าเราไม่ได้คำนวณการเคลื่อนไหวเลย (ในที่นี้คือ Game_input(); และ Game_play();) รูปต่าง ๆ อาจจะไม่มีการเปลี่ยนแปลง แต่ในระบบ animation ในระบบ 3มิตินั้น ตัว mesh สามารถเคลื่อนไหวได้ตลอดเวลา รวมถึง shader ที่สามารถเคลื่อนไหวเปลี่ยนแปลงได้ ค่าของ g_fElpasedTime สามารถเอามาใช้อ้างอิงได้ว่ามีการเปลี่ยนแปลงไปมากแค่ไหน

Function สอง คือ WindowProc

LRESULT CALLBACK WindowProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
switch( msg ) {
case WM_CLOSE: {
...
}
}
}

อันนี้เป็นตัวอย่างนะครับ ที่จะให้ดูคือ function นี้จะรับข้อความมาทางตัวแปร msg แล้วเราก็เอามา switch ดูว่ามันมีค่าอะไรบ้าง ค่าต่าง ๆ เหล่านี้ เช่น WM_CLOSE ลองหาได้จากเวปไซต์ของ Microsoft นะครับ มันมีหลายตัวมา ตั้งแต่พื้นฐาน ไปจนถึงการกดปุ่ม การ click mouse ที่เกิดขี้นใน window ของเรา (การรับค่า input สำหรับเกมเล็ก ๆ เราสามารถใช้ตัวนี้แทน Direct input ได้อยู่นะครับ แต่มันอาจจะช้ากว่าของ Direct input ครับ)
แต่ตัวที่สำคัญคือการปิด window ครับ คือถ้ามีการปิด window เพื่อปิดเกมของเรา แทนที่จะออกจากโปรแกรมแบบปกติ เราสามารถเขียนกรองตรงนี้เพื่อการคือค่าหน่วยความจำตรงนี้ได้
ตัว WindowProc นี้เป็น Callback function นะครับ หมายถึงว่ามันจะถูกเรียกผ่าน Windows เมื่อมี message ผ่านตัว window ที่เราสร้างขึ้น

จบครับ ในส่วนที่ผ่านมานี้ คนที่เคยเขียนโปรแกรมบน Windows อาจจะรู้มาบ้างแล้วครับ ผมจะอธิบายแค่คร่าว ๆ เท่านั้น ส่วนเรื่องรายละเอียดอื่น ๆ เช่น ค่าคงที่ต่าง ๆ ที่ใช้สร้าง window หรือ message ที่ Windows เป็นตัวส่งมานั้น หาอ่านเพิ่มเติมได้ครับ
ในส่วนต่อไป เราจะมาสร้าง object ของ DirectX กันครับ
(สำหรับใครที่รอ source code ขอให้ลองหาที่อื่นก่อนนะครับ พอดีว่าตัวที่ผมมี ไม่มีแบบที่เป็นตัวเบื้องต้นอยู่เลยครับ)

บันทึกนี้เขียนที่ GotoKnow โดย  ใน การเขียนเกมด้วย DirectX



ความเห็น (0)