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

Pretender:    2498
szeki:    2440
Seeting:    2306
Geri:    2188
Orphy:    1893
Joga:    1791
Bacce:    1783
MaNiAc:    1735
ddbwo:    1625
syam:    1491
Xna 01 2010.10.14 17:14


Üdv mindenkinek!

Mi is az XNA? Az XNA egy framework, azaz keretrendszer, ami játékok készítését könnyíti meg. Emögött ott van a teljes .NET, ami szintén nagy segítséget nyújt. Mindezt C# nyelven programozhatjuk. Senki ne akarjon XNA-val AAA játékot készíteni, mert nem erre találták ki, de ez gondolom (remélem) senkinek nem is célja.

Ez a tutorial sorozat főleg a kezdőknek szólna, ám óva intek mindenkit attól, hogy a C# nyelv ismerete, és minimális programozási tudás nélkül kezdjen el játékot fejleszteni. Tudom, hogy unalmas lehet egy-egy számkitaláló, illetve ehhez hasonló programok megírása, azonban fontos. Meg akár hiszitek, akár nem, én nagyon örültem annak is, amikor kitalálhattam, hogy mire gondolt a gép. :)
Próbálom leegyszerűsíteni a dolgokat, így talán könnyen érthető lesz. Jómagam XNA 3.1-et használok, egyelőre szerintem ezt lenne érdemes használni (a 2.0 "elavult", a 4.0-t meg még nem használtam, van benne pár újítás, amik nekem személy szerint nem jönnek be). Ehhez szükségünk van egy legálisan letölthető Visual Studio 2008-ra (ezentúl VS) és a teljes XNA 3.1 Game Studio-ra (XNA GS). Miután feltelepítettük a VS-t, majd az XNA GS-t, indítsuk el! :)

A felépítés:

Lesz egy game library-nk (nevezzük engine-nek, bár ez nem teljesen helyénvaló) amiből dll fog fordulni, illetve egy windows application-ünk, amiből az exe lesz. Az engine oldalra nem játékspecifikus, hanem általános, több játék számára használható dolgokat fogunk rakni. Ez az egész arra jó, hogy, ha másik játékot is szeretnénk csinálni, akkor csak linkeljük a dll-ünket, és máris használhatjuk az abban lévő kódot.

A Game Library

Indítsunk egy új projektet, amit többek között megtehetünk úgy, hogy File -> New -> Project.
Válasszuk ki az XNA Game Studio 3.1-et, azon belül pedig a Windows Game Library (3.1)-et. Megadjuk a nevét, hogy hova kreálja a projektet, illetve megváltoztathatjuk a Solution nevét is, ha szeretnénk. Ha minden megvan, nyomjunk egy OK-t.



Ez automatikusan létrehoz nekünk néhány dolgot, amiből a Content mappára nem lesz szükségünk, így jelöljük ki, majd nyomjunk egy del-t (avagy jobbklikk delete). Van még egy Class1.cs fájl is, ezt nevezzük át Game.cs-re (vagy egy tetszőleges névre). Ekkor megkérdezi, hogy átnevezze-e az osztályt, arra nyomjunk egy Igen-t. Arra figyeljünk, hogy, ha a Game nevet választottuk, akkor az ütközik az XNA Game osztályának nevével. A Game osztályunk lesz a "központunk", a lényegesebb dolgok ezt fogják megkapni konstruktorban paraméterként.

Alapok

Ahhoz, hogy az XNA létrehozzon egy ablakot, inicializálja a device-t, ezt az osztályt örököltetnünk kell a Microsoft.Xna.Framework.Game osztályból.
Kód:
public class Game : Microsoft.Xna.Framework.Game

Ezzel kaptunk néhány felülírható metódust is, amiket használni is fogunk. A C#-hoz szerencsére adtak okos IS (IntelliSense)-t, így ha begépeltük, hogy "override", majd nyomunk egy szóközt, szépen kilistázza az elérhető felülírhatő metódusokat. Ezek közül szükségünk lesz az Initialize, LoadContent, Update, Draw metódusokra. Emellett kelleni fog még egy publikus konstruktor is, ami egyszerűen csak
Kód:
public osztálynév()
{
}

A konstruktorral hozzuk létre magát az "objectet".

Változók

Kelleni fog nekünk egy device manager is, de szerencsére van az XNA-ban beépített osztály.
Kód:
protected GraphicsDeviceManager m_Graphics;


(megj.: rászoktam az ún. prefixek használatára, ami megkönnyíti a változónevek keresését. "m_", mint member, "p_", mint paraméter)

Ezt a konstruktorban példányosítjuk:
Kód:
public Game()
{
    m_Graphics = new GraphicsDeviceManager(this);
}

Továbbá ami elengedhetetlen, az a buffer törlése. Ezt a Draw metódusban tehetjük meg:
Kód:
protected override void Draw(GameTime gameTime)
{
    m_Graphics.GraphicsDevice.Clear(Color.Black);

    base.Draw(gameTime);
}

Ezzel egyelőre kész is vagyunk a Game Library-vel, következhet maga a futtatható program.

A Windows Project

Jobb oldalt láthatjuk a solution explorert. Kattintsunk jobbgombbal a "Solution 'Solutionnév' (1 project)"-re -> Add -> New Project: Válasszuk ki a Windows Game (3.1)-et, írjuk be a nevét, majd nyomjunk egy OK-t.



Ez szintén létrehozott néhány fájt, amiből én ki szoktam törölni a Game.ico, Game1.cs, GameThumbnail.png fájlokat. Most jön az a lépés, amikor az engine-ünket csatlakoztatjuk (linkeljük) a projectünkhöz. Látunk egy references mappát. Nyomjunk rá jobbgombbal, majd "Add reference". Miután megjelent az ablak, nyomjuk át a "Projects" fülre, ahol csak egyetlen egy elem fog szerepelni, az engine-ünk. Nyomjunk egy OK-t.



Ezzel linkeltük a projecthez az engine-ünket. :)

Már csak használnunk kell az engine-t:
Nyissuk meg a Program.cs fájlt. Előszőr is szükség lesz a névtér (namespace) megadására. Nálam az engine neve "XNALessonEngine", így:
Kód:
using XNALessonEngine;

A fájlon belül látni fogunk még egy előre elkészített osztályt, illetve egy statikus metódust. Ami az utóbbin belül van, azt kitörölhetjük. Ennek a helyére fogjuk írni a következőket:
Kód:
Game game = new Game();
game.Run();

Nincs más dolgunk, mint elsődleges (Startup) projectté tenni a Windows Game projectünket. Ehhez kattintsunk rá jobbgombbal a solution explorerben, majd "Set as StartUp Project". Ezután, ha nyomunk egy F5-öt (futtatás), láthatjuk munkák gyümölcsét, egy fekete hátterű ablakot :) (egyelőre alt+f4-el tudjuk bezárni)

Konfigurálhatóság

Nyilván szeretnénk állítani az ablakunk méretét, a vsyncet, és egyebeket. Én ezt a következő képpen oldottam meg: Készítettem egy "ConfigFile" nevű osztályt, amibe elhelyeztem néhány változót (ablak neve, szélesség, magasság, teljes képernyő-e, vsync-e). Ezt átadom a Game osztálynak paraméterként, ami ebből kiolvassa az adatokat, majd használja is őket. Lássunk akkor, hogy én hogy csináltam :)

Xml

A ConfigFile olvasásához / írásához Xml deserializert / serializert használok. Ehhez elkészítettem egy helper osztályt (ami azért lássuk be, nem a legszebb megoldás), a neve legyen SerializerXml (szinte minden osztályt publikussá szoktam tenni, ez legyen az alapértelmezett, írni fogom külön, ha nincs rá szükség / nem akarjuk, hogy lássák kívülről is)
Kód:
public class SerializerXml
{
}

Készítettem egy Load, illetve egy Save metódust is, ezeknek paraméterként átadva tudjuk megadni, hogy hova mentse / honnan töltse be.
Kód:
public virtual void Load(string p_Path)
{
}

public void Save(string p_Path)
{
}

A "virtual" azt jelenti, hogy ha ebből az osztályból örököltetünk, akkor az nem kötelezően felül tudja írni az adott metódust. Kell maga a file, amiből olvasni fogunk, illetve a serializernek szüksége van a "típusára" is. (protected, hogy a gyerek osztály láthassa, módisíthassa)
Kód:
protected Type m_Type;
protected object m_File;

Kelleni fog ezeken kívül még két namespace, a System.Xml.Serialization és a System.IO.

Nézzük a Load metódust:
Létrehozunk egy xml serializert
Kód:
XmlSerializer serializer = new XmlSerializer(m_Type);

és egy stream-et, aminek a segítségével megnyitjuk és olvassuk a fájlunkat. Ide lehet tenni loggolást, illetve try {} catch {} szerkezetet, aki jónak látja.
Kód:
Stream stream = File.OpenRead(p_Path);

a stream és xml serializer segítségével beolvassuk a fájlból az adatokat, amit az m_File-ban tárolunk
Kód:
m_File = serializer.Deserialize(stream);

nincs már szükség a stream-re, lezárhatjuk.
Kód:
stream.Close();


A Save metódus is hasonlóan néz ki, nem részletezném, a különbség csupán annyi, hogy nem megnyitjuk és olvassuk, hanem létrehozzuk a fájlt, illetve írunk bele:
Kód:
XmlSerializer serializer = new XmlSerializer(m_Type);
StreamWriter writer = File.CreateText(p_Path);
serializer.Serialize(writer, this);
writer.Close();


ConfigFile

Ezután kell a ConfigFile osztály, hozzuk létre az engine-n belül, aminek a szülője legyen az előbb elkészített SerializerXml! A konstruktorban beállíthatunk majd default értékeket a változóknak, én ettől most eltekintek. Ami viszont fontos, hogy a típust állítsuk be!
Kód:
public class ConfigFile : SerializerXml
{
    m_Type = typeof(ConfigFile);
}

Ha nem akarunk külön mindenféle megszorításokat tenni (az xml serializer "lássa-e" és egyebek), akkor írjuk csak szimplán publikusnak a változóinkat.
Kód:
public string m_Title;
public int m_Width;
public int m_Height;
public bool m_Fullscreen;
public bool m_Vsync;

Ezután következzen maga a betöltés:
Mint mondtam fentebb, a "virtual" miatt felülírható a gyerek osztályban az adott metódust, amit mi ki is használunk:
Kód:
public override void Load(string p_Path)
{
    base.Load(p_Path);
}

Amikor a "base.Load(p_Path)" részhez ér, meghívja a SerializerXml Load metódusát, ami beolvassa a fájlt. Ebből következik, hogy a base.Load után tudunk dolgozni az adatokkal. Van egy általános m_File objectünk. Ebből kell nekünk egy ConfigFile. Ezt elérhetjük így:
Kód:
ConfigFile file = (ConfigFile)m_File;

az új lokális "file"-ban bennevannak a beolvasott adatok, már csak be kell állítanunk őket.
Kód:
m_Title = file.m_Title;
m_Width = file.m_Width;
m_Height = file.m_Height;
m_Fullscreen = file.m_Fullscreen;
m_Vsync = file.m_Vsync;

Ezzel meg is volnánk a ConfigFile elkészítésével, már csak használnunk kellene.

A Game osztályba deklaráljunk egy új változót:
Kód:
protected ConfigFile m_ConfigFile;

Később nem árt, ha elérjük (például majd az Options screenen belül), így írtam rá egy ún. Property-t is:
Kód:
public ConfigFile ConfigFile
{
    get { return m_ConfigFile; }
}

Ez pedig megközelítés kérdése, hogy átadjuk a konstruktorban paraméterként a kész, beolvasott ConfigFile-t, vagy ott olvassuk be, én az előbbit választom:
Kód:
public Game(ConfigFile p_ConfigFile)
{
    m_ConfigFile = p_ConfigFile;

Mivel a Title nem szokott változni, így azt a konstruktorban be is állíthatjuk:
Kód:
Window.Title = m_ConfigFile.m_Title;

Viszont gondolván arra, hogy később akár menet közben szeretnénk állítani pl. a felbontást, a beállítások érvényesítését kitettem egy külön, publikus metódusba:
Kód:
public void UpdateGraphics()
{
    m_Graphics.PreferredBackBufferWidth = m_ConfigFile.m_Width;
    m_Graphics.PreferredBackBufferHeight = m_ConfigFile.m_Height;
    m_Graphics.IsFullScreen = m_ConfigFile.m_Fullscreen;
    m_Graphics.SynchronizeWithVerticalRetrace = m_ConfigFile.m_Vsync;

    m_Graphics.ApplyChanges();
}

Ezt nekünk még meg kell hívnunk az Initialize metódusban a base.Initialize() előtt, hogy az ablak létrehozásakor már éljenek a beállítások.
Kód:
protected override void Initialize()
{
    UpdateGraphics();

    base.Initialize();
}

Ezzel az engine oldalon készen is vagyunk, már csak használnunk kell az új funkciót.

Mivel egy-egy játék tartalmazhat olyan beállítasái lehetősegeket, amik másban nincsenek (azaz az engine résznél nem írtuk be), így készítsünk egy új osztályt, én a Config nevet adtam neki. Az őse legyen az Engine-ünkben található ConfigFile osztály. Arra figyeljünk, hogy a típusát felül kell írnunk
Kód:
public class Config : ConfigFile
{
    public Config()
    {
        m_Type = typeof(Config);
    }
}

Ezután ha szükségesnek tartjuk deklarálhatunk új változókat, felülírhatjuk a Load metódust ugyan úgy, mint a fenti ConfigFile osztályban.

Ezután kell nekünk maga a config fájl. A game projecten belül van egy Content nevű mappa, amit én átnevezek "main"-re. Ehhez szükségünk van a property window-ra, ami ha alapból nincs ott, akkor View -> Properties Window. Kijelöljük a Content mappát, majd a property window-ban átírjuk a Content Root Directory-t "main"-re.



Ha ezt tettük, akkor viszont tudatnunk kell a Game osztállyal is (Program.cs) :
Kód:
game.Content.RootDirectory = "main";

Ha ezzel megvagyunk, kattintsunk rá jobb gombbal, Add -> New Item. A felugró ablakból válasszuk ki az "XML File"-t, nevezzük át, majd nyomjunk Add-ot. (én a létrehozás után átírom a kiterjesztést cfg-re, mert az menő :)). A fájlt kijelöljük, majd a property windoban a Build Action-t None-ra, a Copy to Output Directory-t pedig Copy Always-ra állítjük.
A létrehozott fájlból csak az első sort hagyjuk meg, ezután következhet maga a Config.
Kód:
<Config>
</Config>

Arra figyelni kell, hogy a név megegyezzen az osztály nevével! Ezen a részen belül jöhetnek a változók nevei, értékeik megadása:
Kód:
<m_Title>New title</m_Title>
<m_Width>800</m_Width>
<m_Height>600</m_Height>
<m_Fullscreen>false</m_Fullscreen>
<m_Vsync>false</m_Vsync>

A Program.cs fájlon belül, mielőtt létrehozzuk a Game-t, szükségünk lesz két újabb sorra (Az elérési út nyilván tetszőleges) :
Kód:
Config config = new Config();
config.Load("mainConfig.cfg");

Ezt átadjuk a Game osztály létrehozásakor paraméterként:
[code]Game game = new Game(config);[/code]
Nincs már más dolgunk, mint egy F5-öt nyomni és kipróbálni, hogy tényleg működik :)



Elsőre ennyi fért bele, legközelebb az inputot fogjuk tárgyalni :). Várom a kommenteket, észrevételeket, kérdéseket!

Üdv,
Pretender

Értékelés: 0

Új hozzászólás
Pretender          2010.11.29 19:31
Ja, mert veletlen kihagytam, hogy az a konstruktorban van!
Kód:
public class ConfigFile : SerializerXml
{
    public ConfigFile()
    {
        m_Type = typeof(ConfigFile);
    }
}

Hiba azert nincs, mert ebbol az osztalybol orokoltetunk egy masikat, ahol beallitjuk a tipust.
robee00          2010.11.09 20:08
Kód:
public class ConfigFile : SerializerXml
{
    m_Type = typeof(ConfigFile);
}


itt nekem hibát jelez. ha kiveszem megy és semmi probléma nincs a progiba.
Pretender          2010.10.17 07:12
Nem tudom mennyire szorul magyarazatra a draw, illetve azt pontosan nem tudom, hogy az xna a hatterben mit csinal, de talan ott is csak egy buffer swap mehet, esetleg renderstate beallitasok. Nem terveztem rola irni, de ha fontosnak erzed, es megirod, akkor siman ki lehet egesziteni a cikket
mark576          2010.10.17 01:25
Az xna project és a szerializálást közt félúton elvesztettem a fonalat. Az, hogy mi a draw, mikor mit csinál, meg egyebek, az majd el lesz magyarázva valahol?
Pretender          2010.10.16 17:56
Hop, szerkeszteni es torolni sem lehet, igy kulon postba irom: Ennek a kodjat mutatom be, amennyit latsz, annyi van meg egyelore belole, de a cikkek irasa kozben szeretnem fejleszteni is nyilvan.
Pretender          2010.10.16 17:52
Koszi. A program alatt a keszulo engine-re gondolsz, vagy az xna frameworkre? Az elobbi az mivel meg nincs kesz igy nem, az utobbi pedig nem program.
PGames          2010.10.16 17:47
Jól felépített, hosszú, részletes cikk, csak gratulálni tudok érte!
Észrevételem: Nincs.
Kérdés: Csináltál már ezzel a programmal valamit esetleg?