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 04 2010.10.25 14:20


Üdv!

Ahogy ígértem, ez a lecke képek, azaz spriteok megjelenítéséről fog szólni

Sprite

Igen, végre valami látható dolgot is fogunk csinálni! :)
Mindenek előtt valahova (én a Game.cs-be tettem) osztályon kívül hozzunk létre egy új enum-ot Align néven. Ennek segítségével fogjuk beállítani azt, hogy az adott pozíció a kép bal felső sarkában legyen, vagy éppen a kép közepe legyen, stb.
Kód:
public enum Align
{
    LeftTop, Left, LeftBottom, CenterTop, Center,
    CenterBottom, RightTop, Right, RightBottom
};

Az XNA ún. SpriteBatch-et használ Sprite-ok kirajzolására, hozzunk létre egy új változót a Game osztályon belül, amit szeretnénk, ha látnának is kívülről:
Kód:
protected SpriteBatch m_SpriteBatch;

public SpriteBatch SpriteBatch
{
    get { return m_SpriteBatch; }
}

Ezt a LoadContent metódusban hozzuk létre (nyilván a base.LoadContent() előtt)
Kód:
m_SpriteBatch = new SpriteBatch(m_Graphics.GraphicsDevice);


Ezek után hozzunk létre egy Sprite nevű osztályt (nyilván az engine-n belül)! Elég sok dologra lesz szükségünk, lássuk csak:
- Először is kell maga a Game, amiből megtudjuk a kellő információkat (el tudjuk kérni a device-t, stb.)
- A betöltést a Sprite osztályon belül fogjuk intézni, így tároljuk el a betöltendő textúra nevét.
- Illetve kell maga a textúra is :)
- Nem árt, ha eltárolja az ember, hogy sikeres volt-e a betöltés
- Kell a nemrég elkészített enumból is egy példány, illetve egy 2D-s vektor, ami az eltolást fogja megadni
Kód:
private Game m_Game;
private string m_Filename;
private Texture2D m_Texture;
private bool m_Loaded;
private Align m_CurrAlign;
private Vector2 m_AlignVector;

Ezek után néhány publikus változó, amit kívülről állíthatunk (sokan Property-ket használnak, szerintem így kényelmesebb, viszont kevésbé biztonságos). Sorban a változók:
- a source rectangle-el lehet azt szabályozni, hogy a betöltött textúra mely részét rajzoljuk ki. Teszem azt van egy olyan textúránk, amiben van egy fa és egy kő egymás mellett. Ezzel a téglalappal adhatjuk meg, hogy melyiket rajzoljuk ki.
- position a képernyőn lévő pozíció (bal felső sarok {0;0}, jobb alsó pedig {width;height})
- fill area: ha csak simán skálázni akarjuk a spriteot, akkor kicsit körülményesebb használni, azonban sokszor jól jön; ezzel tudjuk megadni azt, hogy mekkora területet töltsön be a textúra. (jól jöhet például menüháttérnél, ahol ezt a {width;height}-re adjuk meg, így a teljes képernyőt betölti)
- rotate értelemszerűen a sprite elforgatását adja meg
- color pedig nyilván a színét :)
- a sprite effects-ek között található a vertikális- és horizontális tükrözés.
Kód:
public Rectangle m_SourceRectangle;
public Vector2 m_Position;
public Vector2 m_FillArea;
public float m_Rotate;
public Color m_Color;
public SpriteEffects m_SpriteEffect;

Továbbá írtam két property-t: néha jól jön, ha el tudjuk kérni a textúrát, illetve a textúra eredeti méretét (például ha 2x-esére akarjuk nyújtani a spriteot, akkor m_FillArea = OriginalSize * 2)
Kód:
public Texture2D Texture
{
    get { return m_Texture; }
}
public Vector2 OriginalSize
{
    get { return new Vector2(m_Texture.Width, m_Texture.Height); }
}

Következzen a konstruktor, ahol default értékeket be is állítunk a dolgoknak:
Kód:
public Sprite(Game p_Game, string p_Filename)
{
    m_Game = p_Game;
    m_Filename = p_Filename;

    m_Texture = null;

    m_Loaded = false;

    m_CurrAlign = Align.LeftTop;
    m_AlignVector = Vector2.Zero;

    m_Position = Vector2.Zero;
    m_Rotate = 0;
    m_Color = Color.White;
    m_SpriteEffect = SpriteEffects.None;
}

Az m_CurrAlign csak egy enum, abból mi nem tudjuk, hogy mennyivel kell eltolni a sprite-ot a pozícióhoz képest, így írjunk egy segédfüggvényt:
Kód:
private void ComputeAlignVector()
{
    float width = m_FillArea.X;
    float height = m_FillArea.Y;

    if (m_CurrAlign == Align.LeftTop)
        m_AlignVector = new Vector2(0, 0);
    else if (m_CurrAlign == Align.Left)
        m_AlignVector = new Vector2(0, height / 2);
    else if (m_CurrAlign == Align.LeftBottom)
        m_AlignVector = new Vector2(0, height);

    else if (m_CurrAlign == Align.CenterTop)
        m_AlignVector = new Vector2(width / 2, 0);
    else if (m_CurrAlign == Align.Center)
        m_AlignVector = new Vector2(width / 2, height / 2);
    else if (m_CurrAlign == Align.CenterBottom)
        m_AlignVector = new Vector2(width / 2, height);

    else if (m_CurrAlign == Align.RightTop)
        m_AlignVector = new Vector2(width, 0);
    else if (m_CurrAlign == Align.Right)
        m_AlignVector = new Vector2(width, height / 2);
    else if (m_CurrAlign == Align.RightBottom)
        m_AlignVector = new Vector2(width, height);
}

Itt csupán annyit csinálok, hogy kiszámolom az eltolást a beállított align enumhoz képest. Az m_FillArea kezdőértékét a betöltéskor fogjuk beállítani. Kell még ugye magát az enum-ot állítani, ezt egy metódusba tettem ki, mert ilyen esetben szükség van a vektor újraszámolására.
Kód:
public void SetAlign(Align p_Align)
{
    m_CurrAlign = p_Align;
    ComputeAlignVector();
}

Ezzel meg is vagyunk, következzen a betöltés:
A fájlnév alapján be kell töltenünk a textúrát, illetve az m_FillArea és m_SourceRectangle változónak kezdőértéket beállítani. Itt használok try{} catch{} szerkezetet, hátha rossz fájlnevet adunk meg :) (megj.: ki lehet egészíteni a catch-t úgy, hogy catch (Exception ex), így ha loggolni akarnánk, akkor tudjuk a hiba mivoltát is)
Kód:
public void Load(ContentManager p_Content)
{
    try
    {
        m_Texture = p_Content.Load<Texture2D>(m_Filename);
    }
    catch
    {
        return;
    }

    m_FillArea = new Vector2(m_Texture.Width, m_Texture.Height);
    m_SourceRectangle = new Rectangle(0, 0, m_Texture.Width, m_Texture.Height);

    m_Loaded = true;
}

Én írtam egy Unload metódust is, tudomásom szerint a Texture2D-re nem szabad meghívni a Dispose-t, így csak annyit tehetünk, hogy null-ra állítjuk, és reménykedünk, hogy takarításkor a GC ezt is viszi magával :)
Kód:
public void Unload()
{
    m_Texture = null;

    m_Loaded = false;
}

Már csak a kirajzolás van hátra, nézzük azt is. Milyen jó, hogy eltároljuk, hogy sikeres volt-e a betöltés, mert ha nem sikerült, és mégis megpróbálnánk kirajzolni, akkor hibaüzenettel elszállna a programunk. Nincs más dolgunk, mint meghívni a SpriteBatch Draw(...) függvényét
Kód:
public void Draw()
{
    if (m_Loaded)
    {
        m_Game.SpriteBatch.Draw(m_Texture, new Rectangle((int)m_Position.X, (int)m_Position.Y,
            (int)m_FillArea.X, (int)m_FillArea.Y), m_SourceRectangle, m_Color, m_Rotate,
            m_AlignVector, m_SpriteEffect, 0);
    }
}


Tesztelés

Szeretnénk már végre látni valamit! Az SGameplay osztályba deklaráljunk egy új változót, mondjuk
Kód:
private Sprite m_Test;

A Load metódusban pedig hozzuk létre!
Kód:
m_Test = new Sprite(Game, "test"); //itt a "test" a textúra neve elérési úttal együtt a Content mappán belül!
m_Test.m_Position = new Vector2(Game.ConfigFile.m_Width / 2, Game.ConfigFile.m_Height / 2);
m_Test.Load(Game.Content);
m_Test.SetAlign(Align.Center);

Én beállítottam a képernyő közepére, középre igazítva :) FONTOS! A SetAlign metódust csak és kizárólag a Load után hívhatjuk meg, hiszen előtte nem ismert a textúra szélessége és magassága, így nem tudjuk kiszámolni az eltolási vektort!
Az Unload-ban pedig az m_Test.Unload-ot hívjuk meg (emellett én még null-ra is szoktam állítani)!
Arra figyeljünk, hogy a SpriteBatch.Draw-t nem hívhatjuk csak a SpriteBatch.Begin és End között! Azért nem tettem a Sprite osztály Draw metódusába, mert ha több sprite-ot akarunk kirajzolni, akkor elég csak egyszer meghívni a Begin-t meg az End-et (sőt, ajánlott, különben igencsak lassú lesz:))
Kód:
Game.SpriteBatch.Begin();
m_Test.Draw();
Game.SpriteBatch.End();

Kellene nekünk még maga a textúra, amit be szeretnénk tölteni (például használjuk ezt). A content mappára kattintsunk jobbgombbal, Add -> Existing Item. Keressük meg a lementett textúránkat, majd Add. (én átneveztem test-re, nyilván kiterjesztés marad)

Nincs más dolgunk, mint elindítani a programunkat, és gyönyörködni az ott látható sprite-ban :)

Kiegészítés

Az SGameplay Update metódusában mozgathatjuk a spriteunkat, mondjuk jobbra és balra az A és D billentyűkkel. Nem árt időhöz kötni, mert amúgy az FPS függvényében mozogna (minél több az fps, annál gyorsabban menne :)). Ehhez ki kell egészítenünk a Game osztályunkat egy új változóval és property-vel:
Kód:
protected float m_Time;
public float Time
{
    get { return m_Time; }
}

Ennek a konstruktorban adjunk 0 kezdőértéket
Kód:
m_Time = 0;

Az Update metódusban pedig frissítjuk az értéket minden egyes frame-ben
Kód:
m_Time = (float)gameTime.ElapsedGameTime.TotalSeconds;


Ezzel meg is vagyunk, jöhet maga a mozgatás, ami innentől pofon egyszerű: SGameplay Update metódusába szúrjuk be a következő sorokat:
Kód:
if (Game.Input.KeyIsPress(Keys.A))
    m_Test.m_Position.X -= Game.Time * 100;
if (Game.Input.KeyIsPress(Keys.D))
    m_Test.m_Position.X += Game.Time * 100;

Nem árt a 100-as szorzó, különben nem igen vennénk észre a mozgást. :) Nincs más dolgunk, mint futtatni, és kipróbálni!

Remélem tetszett ez a lecke, várok bármilyen kérdést, hozzászólást, észrevételt!



Üdv,
Pretender

Értékelés: 0

Új hozzászólás
Nincs megjegyzés