2010. január 30., szombat

Mozdulj! Mozdulj! Mintha élnél ember...

Nem, nem állt le a fejlesztés. A ritkán megjelenő bejegyzések oka csupán az, hogy a játékot csak és kizárólag szabadidőnkben - ami az én esetemben sajnos a családtól vonódik el - van lehetőségünk fejleszteni. Az pedig nincs sok...
Sebaj, azért haladtunk egy kicsit az elmúlt bejegyzés óta. Szokásunkhoz híven próbálunk madárnyelven írni, hogy a laikusok is - mint mi - nagyjából értsék miről van szó, szóval minden expert-től elnézést kérünk, ha valami irgalmatlan nagy blődséget hordok itt össze a következő fejezetekben.
A legkomolyabb fejlődés az animált modellek támogatása az engine-ben. Röviden arról van szó, hogy találkozni fogunk olyan szereplőkkel is a Hind Action-ben, amelyek geometriája a mozgás során változik, pl. felbőszült modern kori janicsárokkal, akik bizony minden szerénységüket félretéve, RPG-vel próbálnak galambvadászatra invitálni minket. Igen, mi leszünk a galambok.
Az ilyen, animációval rendelkez szereplők leírásához ismernünk kell az összes lehetséges mozgásfázist. Ehhez valami olyan formátumot kellett keresni, ami tárolja mozgásfázisokat. Mivel anno 2004-2005-ben a szakdogámhoz a Quake2-ben prezentált MD2 fájlformátumhoz volt már szerencsém, azt gondoltam, hogy itt is ezt fogjuk használni. Maga a játék - leírni is szörnyű - 13 éve jelent meg, szóval nem mai csirke már ez az MD2 formátum sem, de első körben talán pont megfelelő lesz.
Egy kicsit magyarázom a bizonyítványomat:
Kétség kívül vannak már sokkal jobb formátumok is, ha csak azt vesszük figyelembe, hogy a Quake sorozat ötödik részét heggesztik valahol most is éppen valahol az Id Software-es fiúk. Arról nem is beszélve, hogy egy mai modern játék hasonló modelleinek már a hajszálai több poligonból állnak, mint az MD2 formátum által meghatározott 4096 poly-s maximum. Ez a hátrány kétségkívül a mi esetünkben inkább előny, hiszen madártávlatból nem nagyon fogjuk ezt hiányosságnak érezni - legalabbis remélem. :)
Az MD2 tehát egy bináris fájlformátum, ami, mint ahogy azt fentebb említettem egy karakter geometriáján kívül a mozgási lehetőségeket is képes tárolni. Mint a képleíró, illetve geometriai fájlok általában, ez a fájl is egy header résszel kezdődik. Pontosan most nem fogom felsorolni, mit tartalmaz a fejrész (részletes definíció és infók a formátumról itt), röviden egy leírást ad a karakter jellemzőiről, pl. vertex-ek száma, poly-k száma, textúra koordináták száma, illetve, hogy az adatrészben hol tárolódik a háromszögháló, a keretek definíciói, stb. Lehetőség van a háromszögháló értelmezéséhez használható OpenGL parancsok tárolására, valamint a modell festéséhez használható textúrafájlok (max. 32 féle skin) tárolására is. Előbbit nem használ(hat)juk, lévén a Quake2 eredetileg OpenGL-ben íródott, mi meg most XNA-ban akarjuk használni. A skin-ekről annyit írnék, hogy minden modellnek többféle textúrája volt, ezzel kívánták szemléltetni, hogy a találataink hatására hogyan amortizálódik az éppen hentelt ellenség (pl. vérfoltok).
A geometriai információk feldolgozására most nem térnék ki, ami minket jobban érdekelhet, az a mozgásfázisok kezelése. A karakter összes mozgása kulcskeretekben van definiálva, amelyeket több fázisra oszthatunk (pl. áldogál, fut, lő, szenved, meghal, stb.). Az animálás jelenthet egyszeri (várhatóan meghalni csak egyszer fog, azaz a halál fázist elég egyszer lejátszani), vagy ciklikus (fut) fázislejátszást. Minden fázisnak van kezdete, vége és lejátszási sebessége. Ezeket az információkat összevetve a modellidővel meghatározható, hogy pontosan melyik keretet kell bemutatnunk. Mivel a kulcskeretek egész számokhoz kapcsolódnak, valahogy fel kell dolgoznunk azokat az kereteket is, amelyek azonositója nem egész szám. Ellenkező esetben azt tapasztalnánk, hogy az animáció "akadozik", mivel nem kezeljük le a két kulcskeret közötti állapotokat. Ezt elkerülendő a két kulcskeretet jelölő egész szám közti keretet interpolálni kell, így folyamatos mozgást kapunk.
Az XNA framework természetesen alapból nem támogatja az ilyen típusú modellek betöltését, megjelenítését. Ahogy korábban említettük, fejlesztés közben folyamatosan szem előtt szeretnénk tartani a cross-platform futtatás lehetőségét, azaz, hogy PC mellett XBOX360-on is futtatható legyen a játék. Jelenlegi ismereteim szerint a konzolon mindenféle file-ok beolvasása nem kimondottan szerencsés, lehetőleg kerülendő. Mi akkor a megoldás? Szerencsére itt van nekünk a content-pipeline.
A content-pipeline teszi lehetővé, hogy mindenféle content-et (hangok, képek, modellek, stb.) be tudjunk tölteni a játékunkba. Természetesen számos ismertebb formátum (képformátumok, directx modellformátum, xml, stb.) alapból betölthető, azaz a framework fejlesztői leimplementálták a megfelelő importer, processor logikákat, úgyhogy ezek betöltésével nem kell bajlódnunk. Más a helyzet az MD2-vel. Ha szeretnénk MD2 formátumú fájlt betölteni a content-pipeline-ba (és mivel az XBOX360 támogatás miatt ez számunkra elengedhetetlen), akkor saját importert kell implementálnunk ehhez a fájltípushoz. Szerencsére erre is kínál lehetőséget az XNA framework.
Ádámot megkértem, hogy készítsen egy kis pilot version-t (ezt használtam fel később az MD2 importerhez), ezt fogom most bemutatni:

Röviden arról van szó, hogy van egy sima text fájlunk, ami leír egy háromszöget. Ezt szeretnénk betölteni a content-pipeline-ba, majd a képernyőn megjeleníteni.
A fájl tartalma:

v -0.500000 -0.500000 0.000000
v 0.000000 0.500000 0.000000
v 0.500000 -0.500000 0.000000

Vat tehát három v sor, ami megadja a háromszög három csúcsának (vertex-ének) koordinátáit.
Szükségünk lesz egy osztályra, ami majd tartalmazza ezeket a vertex-eket:

public class Triangle
{
public Vector3 v0;
public Vector3 v1;
public Vector3 v2;
}

A célunk az, hogy a txt fájlunkból legyen egy xnb fájl, amit majd konzolon is betudunk később olvasni. Ehhez írnunk kell egy saját importer osztályt:

[ContentImporter]
public class Importer : ContentImporter<Triangle>

A neve tehát Importer lesz, ezt fogjuk látni később a Visual Studio-ban, amikor a txt fájl felfűzzük a Content fára és beállítjuk a kívánt importert. Amint látjuk, az Importer osztály a ContentImporter template osztályból származik, ahol a template éppen a mi Triangle osztályunk. Az első dolgunk, hogy valamilyen, a .NET által adott módon beolvassuk, feldolgozzuk az információkat a txt fájlból. Nézzük, hogy csinálta Ádám:

public override Triangle Import(string filename, ContentImporterContext context)
{
List<Vector3> vectors = new List<Vector3>();

foreach (string[] line in GetLines(filename))
{
if (line[0] == "v")
vectors.Add(ParseVector3(line));
}

Triangle triangle = new Triangle();

triangle.v0 = vectors[0];
triangle.v1 = vectors[1];
triangle.v2 = vectors[2];

//majd visszadobjuk, es vegeztunk is.
return triangle;
}

Van tehát egy Import metódusunk, ami bejövő paraméterként megkapja a txt fájlunk nevét, a visszatérési értéke pedig egy Triangle típusú objektum. Röviden annyit csinálunk, hogy beparsoljuk a vektorokat egy listába, majd létrehozunk egy tirangle objektummot, amit szépen feltöltünk a három vertex-el, majd visszaadjuk. A GetLines() és ParseVector3() metódusok most nem lényegesek.
Most eljutottunk tehát odáig, hogy be van töltve a memóriába egy triangle objektum, amibe belepakoltuk a txt fájlból kiolvasott vertex koordinátákat. Mind a hármat.
Az ContentImporter-el beolvasott fájlokat az XNA framework egy úgynevezett XNB kiterjesztésű fájlba konvertálja, ez azt jelenti, hogy futásidőben már csak erre a titkosított XNB fájlra lesz szükségünk, a release tehát nem kell, hogy tartalmazza az eredeti txt fájlt.
Természetesen az XNA framework most még nem tudja, hogyan kellene ezt a txt fájlt XNB formátumba konvertálni, ehhez még kicsit dolgoznunk kell. Két osztályt kell még létrehoznunk:

[ContentTypeWriter]
public class Writer : ContentTypeWriter<Triangle>

{
protected override void Write(ContentWriter output, Triangle value)
{
output.Write(value.v0);
output.Write(value.v1);
output.Write(value.v2);
}

public override string GetRuntimeReader(TargetPlatform targetPlatform)
{
return typeof(Reader).AssemblyQualifiedName;
}
}

Először szükségünk lesz egy ContentTypeWriter osztályra. Ez nagyjából annyit definiál, hogy a kívánt adatok milyen sorrendben kerüljenek bele az XNB fájlba. A Write metódus két bejövő paraméterét felhasználva írhatunk a ContentReader-be. Amint látható, szépen beleírjuk a három vertex-ünket. A GetRuntimeReader() metódusban definiálhatjuk a Writer-hez a megfelelő Reader-t.
Nézzük most meg a beolvasási oldalt. Ezt hajtuk végre az XNB fájl beolvasásakor:

[ContentTypeReader]
public class Reader : ContentTypeReader<Triangle>

{
protected override Triangle Read(ContentReader input, Triangle existingInstance)
{
Triangle triangle = new Triangle();

triangle.v0 = input.ReadVector3();
triangle.v1 = input.ReadVector3();
triangle.v2 = input.ReadVector3();

return triangle;
}
}

Amint látható a Read metódusunk visszaad egy Triangle típusú osztályt, amit a ContentReader típusú input objektum segítségével töltünk fel. Arra kell csupán figyelnünk, hogy a kiírt, majd beolvasott adatok sorrendje, típusa és mérete szinkronban legyen.

Ezzel meg is volnánk az Importer-el. Semmi más dolgunk nincs, mint a Game osztályban a Content.Load(assetName) utasítással betöltsük, majd megjelenítsük az immár content-pipeline ready háromszögünket:

Triangle triangle = Content<Triangle>.Load("asd");

Ahol az 'asd' a felfűzött txt fájlunk Asset Name értéke.

Lássuk az eredményt:
















És végül egy videó, hogy is néz ki mindez a játékban jelenleg:

Nincsenek megjegyzések:

Megjegyzés küldése