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 05 2010.10.25 14:22


Üdv mindenkinek!

Folytatnám egy kicsit a sprite témakört: ebben a leckében az animált sprite-ról írnék.

Animált sprite

A mozgás ún. frame-kből, azaz képkockákból áll. A képben való tárolást úgy kell elképzelni, mintha egy filmszalagot látnánk, a képkockák egymás mellett helyezkednek el (lehet olyan eset is, hogy több sorban rendezik el őket, én most az előbbivel foglalkozok, ez alapján írtam meg az osztályt)
Mitől lesz nekünk animált? Az előző leckében emléítettem a SourceRectangle-t, ez pontosan erre való: meg lehet vele adni, hogy a kép melyik részét szeretnénk kirajzolni. A legegyszerűbb esetet tekintsük mostantól: egy sorban konstans (állandó) szélességgel rendelkező frame-k külön képekben vannak eltárolva. (Lehetett volna például úgy is, hogy az összes animációs fázis (álldogál, fut, ugrik, stb.) egy képen belül van elrendezve, azt egy kicsivel komplikáltabb kezelni, de az sem bonyolult)




Mindenek előtt egy apró módosítás a Sprite osztályban: a Load metódust tegyük virtuálissá!
Kód:
public virtual void Load(ContentManager p_Content)


Készítsünk egy SpriteAnimated (lehetne akár AnimatedSprite is, vagy bármilyen név, én a Sprite kezdődés miatt könnyebben megtalálom így) nevű osztályt, aminek az őse legyen a Sprite osztályunk!
Vegyük sorba, hogy mire lesz szükségünk:
- egy képkocka szélességére, hogy tudjuk, hogy mekkora területet kell kirajzolni, illetve mennyivel kell "odébbléptetni"
- a képkockák számára, hogy tudjuk, hogy véget ért-e már az animáció (meg lehetne határozni az időből is)
- a jelenlegi képkocka indexére, hogy tudjuk, hogy éppen hol tartunk :)
- tudnunk kell, hogy szeretnénk-e ismételni az adott animációt
- arra az időre, amég egy képkockát látunk (másodpercben)
- illetve a jelenlegi időre, hogy tudjuk, hogy kell-e a következő frame-re lépnünk
Tehát ezek sorban:
Kód:
private int m_FrameWidth;
private int m_FrameCount;
private int m_CurrFrame;
public bool m_Loop;
public float m_FrameTime;
private float m_CurrTime;

Írtam még egy property-t is, amivel egyszerűen azt kérjük el, hogy az adott animáció végére értünk-e
Kód:
public bool AnimationEnded
{
    get { return m_CurrFrame >= m_FrameCount; }
}

Nézzük a konstruktort: én itt szoktam átadni paraméterként az olyan változókat, amik nem változnak meg, ilyen például a frame szélessége, illetve a frame-k száma. A konstruktoron belül beállítok default értékeket is a változóknak.
Kód:
public SpriteAnimated(Game p_Game, string p_Filename, int p_FrameWidth, int p_FrameCount)
    : base(p_Game, p_Filename)
{
    m_FrameWidth = p_FrameWidth;
    m_FrameCount = p_FrameCount;
    m_CurrFrame = 0;
    m_Loop = false;
    m_FrameTime = 1.0f;
    m_CurrTime = 0.0f;
}

Valahogy tudatni is kell az ősosztállyal, hogy mi a frame szélessége, ezt a Load metódusban meg is tesszük:
Kód:
public override void Load(ContentManager p_Content)
{
    base.Load(p_Content);

    m_FillArea.X = m_FrameWidth;
    m_SourceRectangle.Width = m_FrameWidth;
}

Néha szükség lehet arra, hogy kívülről is tudjuk "resetelni" az animációt, ezért publikussá teszem a metódust, azonban, ha nem szeretnénk kívülről látni, ám az engine oldalon belül publikusként szeretnénk kezelni, akkor a public helyére írjunk "internal"-t. Ez az adott projekten belül publikusként, azonban egy másik projektből tekintve privátként viselkedik .
Kód:
public void ResetAnimation()
{
    m_CurrFrame = 0;
    m_CurrTime = 0.0f;
}

Következzen az Update metódus. Itt frissítenünk kell az m_CurrTime-t, az m_CurrFrame-t, szükség esetén resetelni az animációt, illetve beállítani az m_SourceRectangle kezdő, azaz X értékét. Lássuk:
- hozzáadom az eltelt időt a jelenlegi időhöz
- megvizsgálom, hogy ha ez az idő nagyobb, mint amit megadtuk, hogy meddig lássuk az adott frame-t, akkor léptetjük, és reseteljük az időt.
- megnézzük, hogy véget ért-e az animáció, azon belül két lehetőségünk van: ha be van állítva ismétlésre, akkor kezdjük előlről, ha nincs, akkor pedig semmi más dolgunk nincs, "kilépünk" az adott függvényből.
- frissítjük a source rectangle-ünk kezdőértékét, attól függően, hogy hanyadik frame-ben járunk.
Kód:
public void Update()
{
    m_CurrTime += m_Game.Time;

    if (m_CurrTime > m_FrameTime)
    {
        m_CurrFrame++;
        m_CurrTime = 0.0f;
    }

    if (AnimationEnded)
    {
        if (m_Loop)
            ResetAnimation();
        else
            return;
    }

    m_SourceRectangle.X = (m_CurrFrame * m_FrameWidth);
}

Ezzel itt készen is vagyunk :)

Az egyszerűség kedvéért írtam egy néhány sorból álló osztályt is, ami megkönnyíti a sprite-ok váltását. Ez lesz a SpriteAnimatedPlayer: Egyetlen dologra lesz csupán szükségünk: mi az a sprite, amit éppen frissíteni és kirajzolni szeretnénk (néha jól jöhet, ha látjuk is kívülről)
Kód:
private SpriteAnimated m_Current;

public SpriteAnimated Current
{
    get { return m_Current; }
}

Emellett lesz még három függvényünk, a SetAnimation, Update és Draw. A SetAnimation fügvénnyel tudjuk majd beállítani, hogy melyik sprite-al dolgozzunk.
Kód:
public void SetAnimation(SpriteAnimated p_Sprite, bool p_Reset)
{
    if (m_Current != p_Sprite || p_Reset)
    {
        m_Current = p_Sprite;
        m_Current.ResetAnimation();
    }
}

A p_Reset azt a célt szolgálja, hogy ha ugyan azt az animációt szeretnénk lejátszani mégegyszer, mint ami be van állítva, akkor meg tudjuk tenni :)
Az Update és a Draw nagyon egyszerű, csak frissítjük és kirajzoljuk az adott sprite-ot:
Kód:
public void Update()
{
    m_Current.Update();
}

public void Draw()
{
    m_Current.Draw();
}

Ezzel az engine oldalon készen is lennénk, lássuk hogyan működik.

A game projekt

Először is szereznünk kell egy animált sprite képet. Én ezt a projektet töltöttem le, és szedtem ki belőle néhány képet. Nézzük például a Sprite\Player mappában megtalálható Run.png-t. Adjuk hozzá a content-ekhez.

Most pedig a kód: kell az előbb elkészített SpriteAnimatedPlayer, illetve azon Sprite-ok listája, amelyeket le szeretnénk játszani az adott playerrel. Ezek nekem:
Kód:
private SpriteAnimatedPlayer m_Player;
private SpriteAnimated m_PlayerRun;

A Load metódusban pedig hozzuk létre őket. A beállításokat lehet próbálgatni, elég egyértelmű mind, és át is beszéltük őket
Kód:
m_Player = new SpriteAnimatedPlayer();

m_PlayerRun = new SpriteAnimated(Game, "run", 64, 10);
m_PlayerRun.m_Loop = true;
m_PlayerRun.m_FrameTime = 0.075f;
m_PlayerRun.Load(Game.Content);
m_PlayerRun.SetAlign(Align.CenterBottom);

m_Player.SetAnimation(m_PlayerRun, false);
m_Player.Current.m_Position = new Vector2(Game.ConfigFile.m_Width / 2, Game.ConfigFile.m_Height);

Értelemszerűen meg kell hívnunk az Update és a Draw metódust is a megfelelő helyeken. Update-t az Update-ben, Draw-t a Draw-ban :)
Kód:
//update
m_Player.Update();

//draw
Game.SpriteBatch.Begin();
m_Player.Draw();
Game.SpriteBatch.End();


Nincs más dolgunk, mint futtatni a programot, ahol azt fogjuk tapasztalni, hogy az emberkénk fut a képernyő alján, középen (egyelőre egy helyben) :)



Remélem tetszett ez a lecke is, várok bármilyen kérdést, hozzászólást, észrevételt! Legközelebb előreláthatóan rectangle-rectangle ütközésről, player osztályról, enemy osztályról lesz szó :)

Üdv,
Pretender

Értékelés: 0

Új hozzászólás
Asylum          2010.10.29 16:55
Csak elmélkedek Én inkább egy olyan algoritmusra gondoltam ami a csontokhoz (amik például bézier görbékként adottak) igazítja a texeleket valamilyen szabály szerint.
Pretender          2010.10.29 13:53
Ha jol ertem arra gondolsz, hogy szeleteljuk fel tobb reszre (test, kar, lab, stb.), es azokat mozgassuk csontokkal? A gond valoszinuleg annyi lenne, hogy torzulna a sprite, vagy nem tudom.
Asylum          2010.10.29 00:39
Egyébként nem is lenne olyan rossz ötlet egy sprite-nak is csontvázat csinálni és az alapján transzformálni a képkockákat.
Pretender          2010.10.27 18:08
skinned mesh, es 3d-ben renderelgetni
Asylum          2010.10.27 17:49
Engem inkább az érdekelne, hogy azt hogy lehet megoldani, hogy a képkockák számától függetlenül tudjam tetszőleges sebességgel lejátszani az animáciot, de ugy hogy ne legyen darabos. Vagyis mintha a framek között interpolálna