játékfejlesztés.hu
FórumGarázsprojectekCikkekSegédletekJf.hu versenyekKapcsolatokEgyebek
Legaktívabb fórumozók:
Asylum:    5457
FZoli:    4892
Kuz:    4455
gaborlabor:    4449
kicsy:    4304
TPG:    3402
monostoria:    3284
DMG:    3172
HomeGnome:    2919
Matzi:    2521

Pretender:    2498
szeki:    2440
Seeting:    2306
Geri:    2190
Orphy:    1893
Joga:    1791
Bacce:    1783
MaNiAc:    1735
ddbwo:    1625
syam:    1491
(C++) DirectX programozás 1. - Bevezetés, Windows ablak létrehozása, Idõzítõ 2005.08.29 02:56



DirectX programozás 1. - Bevezetés, Windows ablak létrehozása, Időzítő


Előszó: Örömmel jelenthetem, hogy belevágunk egy újabb fantasztikus tutoriál-sorozat leközlésébe, amit Crusader követett el (DirectX programozás). A sorozatban a DirectX programozásáról lesz szó (elsősorban 2d szempontból: DirectDraw) C++ nyelven, de érinteni fogjuk magának a Windowsnak a programozását is. Továbbá olvashatunk majd a DirectInput-ról, és az Ogg Vorbis fájlok lejátszásáról, ezenkívül hasznos matematikai és objektumorientált programozási ismeretekre is szert tehetünk. HomeGnome


Bevezetés

Köszöntelek kedves olvasó!

Kezdeném azzal, hogy ajánlom a C++ nyelvet és a DirectX-et, C++ alatt, és ez nem Delphi gyalázás akar lenni! Csupán azt mondom, hogy szerintem a DirectX legkiforrottabban C++ alatt használható, főleg Visual C-vel (bingó, mindkettő Microsoft termék)! Tehát ajánlom a C++-t és a DirectX-et! És most már nem feltétlenül szükséges Visual C!!! A linkeknél található BloodShed weblapról letölthető egy ingyenes fordító(Dev-C++) és hozzá a DirectX 9.0! És mindezek kis méretben!!!

A linkeknél megtalálható még a 8.0-s DirectX, ami nekünk TÖKÉLETESEN megfelel!!!! Kicsi és jó!

Miért is írtam ezt a cikksorozatot?
Először egy kis fejtágítás: hány magyar weblap szól a DirectX programozásáról? Van pár. De sajnos mindegyik DirectX 3D programozásról szól, ami jó, de szerintem egy hobbi programozó(értem ezalatt a tanulókat esetleg, vagy olyan embereket, akiknek nem a nap 20 óráját teszi ki a programozás) nem tud egyedül véghez vinni egy 3D-s programot, mert már a megjelenítés nagy falat+az ütközésvizsgálat, interface, stb. De emlékszünk a pl. a Tycoon játékokra(sokat emlegetem majd őket)? Élvezetesek voltak ügye? Sokan azzal bajlódnak, hogy mindent tudnának hozzá, csupán egy hiányzik: egy bitkép megjelenítése a képernyőn(azaz sok bitkép megjelenítése). Ugyanis a 2D játékokat könnyebb megírni, mint egy 3D-s játékmotort. Gondoljunk bele: ilyen 3D-s progikon egyes cégeknél akár 10-100 ember dolgozhat éveken át! Matematika szerint egy ember mennyi idő alatt végzi el ezt a melót? Szóval javaslom a 2D-s játékokat! És mi kell ehhez? Programozói tudás+megjelenítés(ami elég bonyolult ahhoz, hogy segítséget használjunk). Az utóbbiban szeretnék segíteni.

Hogy mit is fogunk programozni? Elsősorban játékorientált cikkeket kívánok létrehozni, ezért több célszerű trükköt szeretnék bemutatni. Először a DirectDraw megjelenítésbe merülnék bele(2D), aztán a 3D-be, majd jöhet a DirectInput, és esetleg a DirectSound. Előrebocsátom, hogy inkább a netről és könyvekből összeszedett tudás összegzése ez az oldal, illetve angolról való szabad fordítással megszerzett tudás leírása.

Ilyenkor azt hiszem szokás pár szót szólni rólam is. Nem vagyok hivatásos programozó, csupán hobbyból űzöm, azt viszont már jó ideje. A nagyobb programnyelveket ismerem: C, C++, Delphi, Visual Basic. Ezek közül a C++-t részesítem előnyben, bár tény, hogy a Delphi alkalmassabb dialógusablakokat kezelő programok írására. Mivel a programozást hobbyszinten űzöm, ezért hibáimért és tudásom esetleges hibáiért kérlek szólj. Próbálom tudásomat megosztani azokkal, akik ugyancsak érdeklődnek a DirectX programozás iránt, így ez nem biztos, hogy a legprecízebb leírás lesz.

Amit illik tudni, mielőtt belevágsz: csak C++-ban fogunk programozni! Ezért javaslom, hogy előtte tanuld meg a C nyelvet alaposan, és igaz, hogy használni fogjuk a C++-t, de ezt DirectX alatt nem muszáj tudni. Bár az is igaz, hogy az egyéb dolgok közt írni fogok az objektumorientált programozásról is.

Utolsó megjegyzésem, hogy ajánlanám a sorrend betartását, főleg a megértés miatt. Ez alól kivétel az Egyéb ismeretek, ugyanis azt akármikor elkezdhetjük. Ha mégis igényelne előismereteket, akkor azt a cikk elején megemlítem. Természetesen a C nyelv ismerte alapkövetelmény!


Windows ablak létrehozása

Előrebocsájtanám, hogy aki kifejezetten Windowsban akar programozni, az olvassa el a linkeknél található jegyzetet, melyet a BME AUT tanszéken írtak. Áttekinti az egész Windows programozást. Most pedig csak a nekünk leghasznosabb dologról írnék: az ablak létrehozásról!

Egy windows-os program működése elég bonyolult első hallásra. Ugyanis a progi eseménykezelésen alapszik... Magyarázat: ha valamit csinálunk, pl. kattintunk az ablakban, billentyűt nyomunk, akkor azt a windows elküldi a programnak, ami kiértékeli, majd a megfelelő rutint(amit akár mi is írhatunk) végrehajtja.

Most csupán egy teljes képernyős DirectDraw programhoz való ablak létrehozását ismertetném(amiből ESC-vel ki lehet lépni). Többre sajna nincs hely, mert akkor a Weblap címe Windows programozás lenne! Gombokat, Listbox-okat nem fogunk használni (bár ha akar valaki, akkor írhat, segítek).

Na akkor, mivel Dev-C++-t használunk, ezért már létrejött egy program, ami fordítás után egy ablakot jelenít meg. Ez üres, csupán a kilépőgombja működik(megjegyzem ez sem tudja az ablak alapból, meg kell írni a kilépő rutint is!).

Akkor lássuk, mi is az a sok kód, amiből ez a program áll! Egy ablakhoz kell egy HWND tipusú változó, ami igazából az ablak ismertető jele. Ez fogja az ablakunkat jelenteni. Mint látjuk, a program az int WINAPI WinMain (...) funkciónál lép be. Ez lesz a programunk fő függvénye, azaz innen hívhatjuk a többit. Ezután jönnek az ablak tulajdonságait beállító programrészek, melyek egyértelműek (vagy nem fontosak nekünk). Hozzáfűzni csupán ehhez a sorhoz lehet:

wincl.lpfnWndProc = WindowProcedure;

Ez az eseménykezelő függvényt, rutint határozza meg(a függvény mutatóját kéri, aminek a típusa az alábbi:

typedef LRESULT CALLBACK WindowProcedure(HWND, UINT, WPARAM, LPARAM);

Ez a függvény a program alján meg is található, működéséről később. Ezután regisztráljuk az ablakot, így a windows tudni fog róla:

if(!RegisterClassEx(&wincl)) return 0;

Mint látjuk, ha nem sikerül, kilép a programból. Most jön a lényeg, mert létrehozzuk az ablakot:

hwnd = CreateWindowEx(
0,
szClassName,
"Windows App",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
544,
375,
HWND_DESKTOP,
NULL,
hThisInstance,
NULL
);

Ennek a függvénynek a visszatérő értéke az a HWND, amit az eljén deklaráltunk. Az értékek magyarázata sorrendben:
  1. speciális stílusok(pl. fogadjon-e drag-and-drop fájlokat)
  2. az ablakunk osztályneve
  3. az ablakunk neve(ez látszik a fejlécben, ha elindítjuk)
  4. az ablak stílusa, ebből rengeteg van. Tessék megjegyezni, fontos lesz még!
  5. az ablak bal felső pontjának első koordinátája
  6. az ablak bal felső pontjának második koordinátája
  7. az ablak szélessége
  8. az ablak magassága
  9. az ablak "szülője", kitöl ered(ez esetben az asztal a ludas)
  10. az ablak menüje(ez most nincs)
  11. az ablak kezelője(WinXP alatt le van tiltva)
  12. létrehozási adatok(nem kell)
Ezután még semmit nem látunk, meg kell jeleníteni a programot(enélkül nem lesz a tálcán se, csak CTRL+ALT+DELETE-vel tudjuk bezárni(ergo rejtett lesz)):

ShowWindow(hwnd, SW_SHOW);

Első paraméter az ablakunk HWND-je, ismertetője, a második a megjelenítés tipusa(pontosabban, hogy meg legyen-e jelenítve, ugyanis igy tudjuk elrejteni is az SW_HIDE paraméterrel). Most jön az eseménykezelő blokk, egy while utasítással. Alap esetben, ha semmihez nem nyúlunk, a GetMessage(...) 0-tól különböző értékkel tér vissza. Ám ha 0-val tér vissza, a progi kilép(0-t küld a PostQuitMessage(0) parancs). A while blokkban két parancs van, ezek elküldik az üzenetet a WindowProcedure-nak(előbb láttuk, hogy az ablakunkhoz ez van hozzárendelve, ezért ide küldi).

Lássuk a WindowProcedure-t. Ebben van egy switch utasítás, ez szétválogatja a beérkezett eseményeket. Ha pl. van egy gomb az asztalon, és nincs ilyen üzenet a switch blokkjában, akkor hiába nyomjuk, mert ugyan a windows elküldi a WM_COMMAND(ez egyébként a gomboké, listboxoké, stb.) parancsot, de a switch-ben nincs felsorolva.

A WindowProcedure azért érdekes, mert a "parancsokat" az üzenetek(message) tartalmazzák, és ezeknek a "parancsoknak" a paraméterei az lParam és a wParam. Ez négy paraméter, mert mindegyik szétbontható alsó és felső részre(egy szó(word) alsó és felső része). Majd látni fogjuk.

A végső visszatérési értéknek a WindowProcedure-hoz hasonló paramétereket tartalmazó DefWindowProc-nak kell lenni. Ez akkor hívódik, ha nem volt feldolgozandó üzenet, így lekezeli a mozgató és átméretező üzeneteket(így nem kell azokat lekezelnünk). Enélkül még áthelyezni sem tudjuk az ablakot.

Lássuk, mi van, ha megnyomjuk az ablakunkon a bezáró gombot! Megnyomáskor a windows elküldi a WM_DESTROY üzenetet a while ciklusnak. Ezek lefordítják, majd elküldik a WindowProcedure-nak. Ez kiválogatja őket, és megtalálja a WM_DESTROY-t. Az alatta lévő parancsokat végrehajtja. Itt ez jelen esetben a PostQuitMessage(0), ami azt éri el, hogy a GetMessage(...) 0 legyen, így a while ciklus végetér. Na, remélem érthető, ha nem, ott az e-mail.


Egy pici változtatás...

Akkor most változtassunk egy picit a progin, mert így könnyebb lesz programoznunk. Deklaráljunk egy új változót, ami azt tartalmazza, hogy fut-e a programunk, vagy nem:

bool app=true;

Alapban természetesen fut, tehát true az értéke. Továbbá deklaráljunk egy új eseménykezelő függvényt(persze a progi elejére!):

void message_pump();

Ennek a definíciója(akárhova):

void message_pump(void){
MSG msg;

if(PeekMessage(&msg, NULL, 0, 0,PM_REMOVE)){
TranslateMessage(&msg);
DispatchMessage(&msg); }
}

Tehát ahányszor meghívjuk ezt a függvényt, annyiszor fogja az eseményeket kezelni. Most töröljük az eddigi eseményelküldő blokkot (az ablak létrehozása utáni while blokk), és hozzunk létre újat:

while(app) {
message_pump();
/*egyéb programrészek*/
}

Gondolom érthető, hiszen csupán az eseménykezelést külön függvénybe tettük. Az egyéb programrészekbe jön majd a megjelenítés(minden képkocka egy-egy ilyen lesz). Namármost, ha ki akarunk lépni, csupán az app=false; parancsot kell kiadni. Így meg is szűnik a program futása, mert a while ciklus véget ér. Tehát az eddigi PostQuitMessage(0); helyére app=false;-t kell írni...


ESC-vel kilépni...

Átfogóan: ha megnyomunk egy billentyűt, akkor a windows elküldi a WM_KEYDOWN eseményüzenetet, tehát már tudni fogjuk, hogy billentyű leütés történt. Na jó, de honnan tudjuk, melyik? Nagyon egyszerű: az wParam paraméterben benne van... Tehát ha a wParam=VK_ESCAPE(ez az ESC kódja), akkor lépjünk ki (switch-el megoldva(későbbi bővíthetőség miatt)):

case WM_KEYDOWN:
switch(wParam) {
case VK_ESCAPE:
app=false;
break;
}
break;

Ezt kell beszúrni a WindowProcedure switch-es blokkjába, így kilép.


Stílusváltás...

Emlékeztek még a CreateWindowEx függvényre? Abban rizsásztam az ablak stílusáról. Na, ez most nagyon fontos lesz, mert az ablak munkaterülete űgyebár az a szürke rész, ami a cím alatt van. De ha egérrel kontrollálunk azon a részen, a windows drága beleszámolja a fejlécet is a koordinátákba!!! Ezért nekünk olyan ablak kell, amin nincs fejléc, se gombok(ezért kellett az ESC-s kilépés), szóval egy szürke téglalap. Ez a stílus a WS_POPUP. Ez kell a WS_OVERLAPPEDWINDOW helyére írni. Így már minden készen áll a DirectDraw programozásásra.


Remélem így minden érthető, bár picit bonyolult, és különben is, gyakorlatban egyszerűbb! Itt van mellékelve az eredeti, Dev-C++ által kreált, és a változtatott, átírt fájl.

ablak.cpp

ablakvalt.cpp


Időzítő

Mire jók azok az időzítők? Időzítésre! Intézzük el őket gyorsan!

Egy időzítő létrehozása Windows környezetben(magyarul kell egy ablak) csupán egy paracs kiadását jelenti. Viszont választanunk kell, hogy mit akarunk: ha éppen idő van, akkor meghívódjon egy függvény, vagy történjen egy esemény. Nézzük az eseményes módszert!

Először is kell egy időzítő, amit az alábbi paranccsal hozhatunk létre:

SetTimer(hwnd,0,1000,NULL);

Az első paraméter az ablakunk hwnd-je, a második az időzítő száma(jele, azonosítója, azaz lehet több is), az időzítő periódus ideje, ilyen időközökben küld jelet(milliszekundumban), az utolsó pedig most nem fontos. Csináljunk most két időzítőt:

SetTimer(hwnd,1,1000,0);

SetTimer(hwnd,2,500,0);

Minden időzítő megküldi a WM_TIMER eseményt(olyan időközökben, amit beállítottunk) , amit az esemény feldolgozóban feldolgozhatunk(ugyanúgy, mint pl. a WM_KEYDOWN eseményt). De honnan tudjuk, melyik küldte a jelet? Egyszerű, a wParam tartalmazza az időzítő azonosítóját. Tehát ezek az időzítők másodpercenként illetve félmásodpercenként meghívják a WM_TIMER eseményt a megfelelő wParam-mal.


Nézzük most a függvényes módszert! A függvény csak TimerProc függvény lehet, amit mi hozhatunk létre:

VOID CALLBACK TimerProc(
HWND hwnd, // handle to window
UINT uMsg, // WM_TIMER message
UINT idEvent, // timer identifier
DWORD dwTime // current system time
);

Írja az MSDN. Tehát megkapja a függvény az ablak hwnd-jét, a WM_TIMER eseményt, az időzítő azonosítóját és a pillanatnyi rendszeridőt. Tehát pl.:

void CALLBACK vmi(HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime)
{

}

Ekkor az időzítő létrehozása az alábbiként néz ki, ha ehhez a függvényhez szeretnénk rendelni:

SetTimer(hwnd,1,1000,(TIMERPROC)vmi);

Természetesen az azonosító itt is akármi lehet, nem csak egy. Tehát ez másodpercenként meg fogja hívni a vmi függvényt. Nem is magyaráznám tovább, azt hiszem egyértelmű.

Egy időzítő lelövése így néz ki:

KillTimer(hwnd,1);

Ahol az első paraméter a birtokló ablak, a második az időzítő azonosítója.


Példaprogram: Timer.rar


Ha valami mégsem tiszta ezek közül, az írjon, szívesen segítek!


Köszönet azért, hogy már idáig elolvastad:

Crusader


Kapcsolódó linkek:
Crusader DirectX programozás honlapja
Itt található Crusader összes tutoriáljának eredetije.
BloodShed
Nagyon jó ingyenes C++ fordító a Dev-C++ weblapja. Itt található hozzá DirectX is.
A DirectX 8 header és lib fájlokat pedig itt tudod letölteni
Régebbi DirectX. Nekünk tökéletesen megfelel! Ezt ki kell tömöríteni, majd a includes könyvtárból a Dev-C++include könyvtárba kell másolni(felül lehet írni a régieket). A lib fájlokat is ugyanígy. A d3dx8d.dll fájlra majd 3D-ben lesz szükség.
Microsoft Developer Net
A legnagyobb programozói oldal, jó angol tudással mindenről a legprecízebb információt kaphatjuk. Legújabb dolgok lelőhelye, Visual Studiohoz DirectX SDK.
Windows programozás jegyzet
A Budapesti Műszaki Egyetem Automatizálási Tanszék honlapján található nagyon jó Windows programozás jegyzet


A cikksorozat további részei:
DirectX programozás 1. - Bevezetés, Windows ablak létrehozása, Idõzítõ
DirectX programozás 2. - DirectDraw inicializálása, használata
DirectX programozás 3. - DirectDraw megjelenítés 1.
DirectX programozás 4. - DirectDraw megjelenítés 2.
DirectX programozás 5. - Izometrikus grafika
DirectX programozás 6. - Matematika összefoglaló 1.
DirectX programozás 7. - Matematika összefoglaló 2.: Lineáris algebra
DirectX programozás 8. - DirectInput
DirectX programozás 9. - DirectSound, Ogg Vorbis lejátszás
DirectX programozás 10. - Izometrikus grafika 2.
DirectX programozás 11. - Objektumorientált programozás

Értékelés: 10.00

Új hozzászólás
Stonebreaker          2007.02.08 08:52
PhoenixZ: én megtaláltam a google-n a jegyzetet, átküldöm neked mailben.
PhoenixZ          2007.02.05 12:04
Hali! Bocsi, de úgy tűnik, a windows programozás jegyzet linkje elévült. Nem tudnád véletlenül e-mailben elküldeni a zozo85ttk@freemail.hu címre? Előre is köszi! -Zoli
buno          2007.01.22 09:26
Hello!
Nekem is hasonló lenne a kérésem, csak nekem a DirectX 8 header és lib fájlok kellenének, ugyanis már az is elavult. Ha tudod, akkor légy szíves küldd el a [email]buno@lauder.hu-ra[/email] a filet.

köszi, Denes
Stonebreaker          2006.12.03 04:36
Helló! Nemrég regisztráltam az oldalon és elkezdtem olvasni ezt a cikket. Mivel még kezdő vagyok gondoltam letöltöm a BME windows programozása jegyzetét. Sajnos a felettéb lévő link már jó régi lehet és nekem nem hozza be. Nem tudnád átküldeni nekem e-mailre? A címem: bindruck@citromail.hu