LinkTr Forumları (Arşiv Ana sayfa) => Oyun Programlama

Konu: İlk DirectX Programı: Adım 1

Sayfa: [ 1 ]

programmer 13.03.2009 16:01:21
Bu yazımda, DirectX uygulamalarının başlangıç noktası olan pencere oluşturma ve DX ortamına geçişi anlatmaya çalışacağım.

Kullanacağımız dil C++ ve derleyici de MS VC++ olduğundan, C++ Builder'da olduğu gibi herhangi bir pencere oluşturma eklentisi söz konusu değil. Bu yüzden pencereyi elle oluşturacağız.

Pencere oluşturmanın ilk adımı WinMain() fonksiyonunun içeriğini doldurmaya başlamak oluyor:

Kod:
#include <windows.h>

INT WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
//...
}

Klasik, DOS altında çalışan C/C++ programlarının yürütüldüğü fonksiyonun adı main() idi, burada ise WinMain(). Zira, artık windows altında çalışıyoruz ve DX programları da Windows'tan başka platformda çalışmaz.

WinMain() deki parametreleri anlatmaya gerek görmüyorum çünkü onlardan son ikisi dışında hiçbirini kullanmayacağız. Şimdi fonksiyonu yavaş yavaş dolduralım:

Kod:
#include <windows.h>

INT WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
           WNDCLASSEX w;
w.cbClsExtra = 0;
w.cbWndExtra = 0;
w.cbSize = sizeof (WNDCLASSEX);
w.hbrBackground = (HBRUSH) (COLOR_WINDOW);
w.hCursor = LoadCursor (NULL, IDC_ARROW);
w.hIcon = 0;
w.hIconSm = 0;
w.hInstance = hInst;
w.lpfnWndProc = (WNDPROC)WndProc;
w.lpszClassName = L"ilk";
w.lpszMenuName = 0;
w.style = CS_HREDRAW | CS_VREDRAW;
RegisterClassEx (&w);

HWND hWnd = CreateWindow ( L"ilk",
L"İlk Win32 Uygulaması",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
640, 480, NULL, NULL, w.hInstance, NULL);

ShowWindow (hWnd, SW_SHOW);

}

Buraya kadar yaptığımız tek şey pencere oluşturmaktı. Pencere oluştururken kullandığımız ilk şey bir WNDCLASSEX öğesi. Bununla, pencerenin yapısal ve bazı mantıksal özellikleri belirleniyor. Şimdi bu öğeyi adım adım inceleyelim:
cbClsExtra, cbWndExtra ve cbSize: İlk iki parametre genellikle sıfır geçilir. WNDCLASSEX ve pencerenin yapısı hakkında extra byte'lar aktarmak istemiyoruz. Son parametre ise önemlidir. Buraya her zaman sizeof (WNDCLASSEX) geçilir. Zira, pencere yapısal bilgilerini aktardığımız yapının boyu 1 WNDCLASSEX boyutundadır.
hbrBackground: Bu parametre, pencerenin arkaplan rengini gösteren HBRUSH yapısında bir öğedir. Pencerelerin renkleri genelde gridir. Buraya aktarılan parametre ile başka renklere de döndürülebilir. Örnekte, arkaplan rengi klasik pencere rengi olan gri olarak belirlenmiştir. Bu da, (HBRUSH)COLOR_WINDOW değerini atamakla olur. Bu satırda, COLOR_WINDOW değeri 5 e karşılık düşer. Başına konulan (HBRUSH) ile de, bu 5 in HBRUSH yapısındaki karşılığı neyse onu aktaracağımızı söylüyoruz; yani tip dönüşümü yapıyoruz.
hCursor, hIcon ve hIconSm: İlk parametre, pencere aktifken kullanılacak imleci, ikinci parametre programın simgesini, son parametre de programın eğer bir küçük simgesi (small-icon) varsa onu işaret eden yapıdır. Programa hususi bir simge ataması yapmak istemedim, ama imleç olsun istedim. Onun için de bu parametreler yukarıda verdiğim değerlerdedir. Bu şekilde, program aktifken klasik ok imleci ve program simgesi olarak da klasik DOS programı simgesi kullanılacak.
lpszMenuName: Programın bir menüsü olacaksa, ona istinaden aktarılacak veri. Menü ve simgeler birer özkaynak (resource) elemanı olduğu için buradan gelecek öğelerin derlenmesi gerekir. DX programlarında da menü olmayacağı için bu parametre 0 geçilir.
style: Pencere stilini belirten parametre. Pencerenin önüne bir başka pencere gelmesi veya tekrardan ön plana alınması, simge durumuna küçültüp geri eski haline döndürülmesi, boyutunun değiştirilmesi gibi durumlarda pencerenin tazelenmesi gerekeceğinden bu parametreye genellikle CS_HREDRAW | CS_VREDRAW aktarılır. Yani, yatay (horizontal) ve düşey (vertical) olarak (REDRAW ların başındaki H ve V ler buradan gelir) tazeleneceğini bildiriyoruz.
lpszClassName: En önemli parametrelerden ilki. Pencere oluşturacağınız zaman, bu pencerenin bir sınıf adı olması gerekir, çünkü pencere oluşturulurken sınıf adı referans geçirilerek oluşturulur. Eğer derleyicinizde character-set olarak Multi-Byte kullanıyorsanız L"ilk" olarak değil, sadece "ilk" olarak geçmelisiniz. Bu parametreye, kafanıza esen string değeri verebilirsiniz.
lpfnWndProc: Bir diğer en önemli parametre olan mesaj fonksiyonu işaretçisi. Bunun olayını anlayabilmek için yukarıda yazılan programı derleyip çalıştırın. Çalıştırdığınızda, 640x480 boyutunda bir pencerenin, 1 sn bile gözükmeden kapandığını göreceksiniz. Bunun nedeni, pencereye ait bir mesaj döngüsü yönlendiricisinin bulunmayışıdır. İşte bu parametreye, kendi yazdığınız bir mesaj döngüsü fonksiyonunun adını parametre olarak geçirmelisiniz. Örnek bir mesaj döngüsü fonksiyonunu aşağıda veriyorum:

Kod:
LRESULT CALLBACK WndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_DESTROY:

PostQuitMessage (0);
break;

case WM_CLOSE:
PostQuitMessage (0);
break;

case WM_KEYDOWN:
if (wParam == VK_ESCAPE) PostMessage (hWnd, WM_DESTROY, 0, 0);
break;

default:
return DefWindowProc (hWnd, message, wParam, lParam);
break;
}

return 0;
}

Yukarıdaki fonksiyon, basit anlamda bir mesaj döngüsünü ifade edebilir. Fonksiyonun adı değişebilir, ama tipi (LRESULT CALLBACK) ve fonksiyon parametreleri (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) asla değişmez. Değişmediği için de, lpfnWndProc parametresine bu fonksiyonun adını aktarırken (WNDPROC) tip dönüşümünü kullanıyoruz. Bu fonksiyona biraz değinecek olursak, basit anlamda, pencereye gelen mesajları değerlendiren bir fonksiyon olduğunu görebiliriz. Tipik mesajlar WM_CREATE, WM_DESTROY ve WM_CLOSE tur. WM_CREATE, pencere oluşturulduğu anda gönderilen bir mesajdır. WM_DESTROY, pencerenin yok edilmesi esnasında gönderilen bir mesajdır. WM_CLOSE da, sistem menüsündeki Kapat menü öğesi, Alt+F4 tuş bileşimi ya da pencerenin sağ üst köşesindeki X tuşu kullanılarak kapanmaya zorlanması durumunda gönderilen bir mesajdır. Siz bu WM_CLOSE için bir içerik yazmazsanız, programı doğrudan kapatamazsınız. Örneğin ben burada, gelen mesaj WM_CLOSE olduğunda PostQuitMessage(0); fonksiyon çağrısı yapılması gerektiğini söylemişim. Yani, sorgusuz sualsiz pencerenin kapatılmasını istiyorum demek oluyor. Kezâ WM_DESTROY için de aynı şey geçerli. WM_KEYDOWN ise bunlardan biraz farklı. Klavyeden herhangi bir tuşa basıldığında gidecek bir mesaj bu. Bu mesaj, kendiyle beraber wParam ve lParam öğeleriyle gelir (WndProc fonksiyonundaki parametreler bunlar). Bu değerlerde, hangi tuş(lar)a basıldığı bilgisi yatar. lParam, shift, ctrl, alt gibi maskeleme tuşlarından hangisine basıldığını, wParam da harf, rakam, F1...F12, Del gibi klasik tuşlardan hangisine basıldığını tutar. Benim yaptığım denetimde, eğer basılan tuş Esc ise, programın kapatılacağı açıkça görülüyor. switch() denetiminde, case: durumlarında belirtilmeyen başka bir mesaj geldiğinde ise windows'un bunu kendine göre varsayılan biçimde değerlendireceğini (taşımaysa taşıma, boyutlandırmaysa boyutlandırma, simge durumuna küçültmeyse simge durumuna küçültme vs.) belirtmiş oluyoruz; çünkü diğer mesajlarla işimiz olmayacak.

Mesaj döngüsü fonksiyonunu da yazdığımıza göre, mesaj döngüsüne girebilir ve penceremizi görebiliriz:
Kod:
MSG msg;
while (1)
{
if (PeekMessage (&msg, hWnd, 0, 0, PM_REMOVE))
{
if (msg.message == WM_QUIT) break;
TranslateMessage (&msg);
DispatchMessage (&msg);
}
}

Klasik bir while döngüsüyle karşı karşıyayız. Koşulumuz ise 1. Yani her zaman. Dolayısıyla mesaj döngüsüne, mümkün olan her zaman giriyoruz. Ta ki bir WM_QUIT mesajı gelene kadar.

Mesaj döngüsüne de girebileceğimize göre, programın son hali şu şekilde olacaktır:

Kod:
#include <windows.h>

LRESULT CALLBACK WndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_DESTROY:

PostQuitMessage (0);
break;

case WM_CLOSE:
PostQuitMessage (0);
break;

case WM_KEYDOWN:
if (wParam == VK_ESCAPE) PostMessage (hWnd, WM_DESTROY, 0, 0);
break;

default:
return DefWindowProc (hWnd, message, wParam, lParam);
break;
}

return 0;
}

INT WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
           WNDCLASSEX w;
w.cbClsExtra = 0;
w.cbWndExtra = 0;
w.cbSize = sizeof (WNDCLASSEX);
w.hbrBackground = (HBRUSH) (COLOR_WINDOW);
w.hCursor = LoadCursor (NULL, IDC_ARROW);
w.hIcon = 0;
w.hIconSm = 0;
w.hInstance = hInst;
w.lpfnWndProc = (WNDPROC)WndProc;
w.lpszClassName = L"ilk";
w.lpszMenuName = 0;
w.style = CS_HREDRAW | CS_VREDRAW;
RegisterClassEx (&w);

HWND hWnd = CreateWindow ( L"ilk",
L"İlk Win32 Uygulaması",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
640, 480, NULL, NULL, w.hInstance, NULL);

ShowWindow (hWnd, SW_SHOW);

MSG msg;
while (1)
{
if (PeekMessage (&msg, hWnd, 0, 0, PM_REMOVE))
{
if (msg.message == WM_QUIT) break;
TranslateMessage (&msg);
DispatchMessage (&msg);
}

}

return 0;
}

Burada yazılanları bir .cpp dosyasına kaydedip derlerseniz, boş bir pencere olarak çalışan bir programdan başka hiçbirşey elinize geçmeyecek Gülümseme Diyeceksiniz ki "oha lan, bu kadar kod sadece pencere oluşturmak için miydi?". Evet, aynen öyle. DirectX ile devam ederken daha beterleri gelecek haberiniz olsun.

Bir dahaki yazımda DX ortamına geçişi anlatmaya çalışacağım.


Sayfa: [ 1 ]