int21h

Uložení animace do jediného obrázku

Dejme tomu, že chcete napsat jednoduchou hru. Umíte základní operace s grafikou, umíte načítat obrázky (PCX, BMP, GIF - je jedno jaké) a teď přemýšlíte, jak budete mít uložené jednoduché animace.
Zdůrazňuji jednoduché. Na komplikované celoobrazovkové animace asi využijete nějaký profesionální formát, třeba FLC. Jenže jak nejlépe uložit třeba lesknoucí se krystal v trávě?
Asi byste se pokusili o definici vlastního formátu. Vymysleli byste něco jako archív, ve kterém by bylo uloženo více obrázků. Každý z těchto obrázků by odpovídal jedné fázi animace. Měl by jednoduchou strukturu:

Archív s animací

0: počet animačních okének (PAO): BYTE
1: array[1..PAO] of longint {pozice začátku obrázku v archívu}
1+PAO*4:Začátek prvního obrázku (např začátek hlavičky PCX)
...
X: Začátek druhého obrázku
...

Všechno, zdá se, funguje, ale brzy zjistíme, že to není ono. Toto řešení je totiž hrozně těžkopádné.
Představte si, že takhle nadefinujete pohyb postavičky, načtete to ve vaší plošinovce a se znechucením zjistíte, že postavička kulhá.
Musíte tedy archív zase rozbalit, pozměnit nevyhovující animační fázi a zase sbalit.
Pokud pracujete na hře sám, tak se to dá překousnout. Pokud na tom děláte ve skupině, tak je to problém.
Co tedy navrhuješ lepšího Laaco?

Já jsem ve své hře Jupír 2 použil jinou metodu, a to rozkreslení všech animačních fází do jediného obrázku. Použil jsem formát PCX, ale to je úplně jedno.
V základní verzi vypadalo takové rozkreslení animace takto:

Trošičku složitější případ byla třeba tato páka:

Abychom si rozuměli - zelený rámeček nebyl součástí obrázku!
Dávám ho sem, jenom aby bylo jasné ohraničení obrázku.

Jak vidíte, klíčovým prvkem takto definované animace je Buňka. Buňkou rozumějte bílý rámeček obsahující něco uvnitř. To uvnitř budeme nazývat animačním snímkem.
První animace tedy obsahuje 11 animačních snímků v jedné animační linii.
Druhá animace obsahuje čtyři animační linie, které mají buďto jednu nebo čtyři animační fáze - dohromady to čítá deset animačních snímků.

Teď si dejme technicko-praktickou vložku. Jak to naprogramovat?
Je potřeba mít načtený v paměti celý obrázek. Ve Freepascalu žádný problém, v Turbo pascalu budeme zápasit s 64KB bariérou. Asi bych poradil dát ho do XMS a odtud po částech načítat. Žádný med, ale na toto omezení jsou programátoři v Turbo pascalu zvyklí, tak si poradí :-)
A za druhé musíte napsat funkci (proceduru) na prohledávání obrázků a vyhledávání buněk. Upozorňuji vás, že to není triviální. Abyste si to usnadnili, tak si pečlivě rozmyslete, jak mohou být buňky uspořádané.
V Jupírovi jsme striktně dodržovali pravidlo, že:
- první buňka má levý horní roh na souřadnici [0,0].
- horní okraje buněk jsou v jedné výšce
- všechny první fáze všech animačních linií mají levou stranu na X souřadnici 0.
- mezi všemi buňkami musí být alespoň jeden pixel mezery.
- buňky jsou vždycky obdélníky

Zase si ale neklaďte tato pravidla příliš svazující. Nechte svým grafikům trochu volnosti - umělecky založení chlapci složitostem programování nerozumějí a nepochopili by, proč chcete aby např. vzdálenost mezi dvěma buňkami horizontálně byla zrovna dvě, a ne třeba tři :-)
Svůj zdroják sem psát nechci, protože jsem tento koncept ještě široce rozvedl a obrazový detektor teď vypadá podle toho - je velice složitý. V zásadě je to ale takováto konstrukce:

Function ObrazovaAnalyza(p:pointer;    {zdrojovy obrazek, ktery zkoumame}
                         x,y,          {pozice v obrazku, od ktere zacneme vyhledavani bunek}
                         sx,sy:longint;{sirka a vyska zdrojoveho obrazku}
                         var sirka,vyska:longint;     {sirka a vyska nalezeneho snimku}
                         var snimek:pointer;          {obrazova data nalezeneho animacniho snimku}
                         var dalsi_x,dalsi_y:longint):byte;    {pozice dalsi nalezene bunky}
{Vysledek funkce muze byt:
0 : to, co jsme nasli, nebyla posledni faze animacni linie
1 : to, co jsme nasli, byla posledni faze animaci linie, nize je ale jeste dalsi linie
2 : KONEC - zadna animacni linie uz v obrazku neni


V tomhle kusu kodu budeme predpokladat, ze zpracovavame 256 barevny obrazek (tedy co bajt, to pixel)
}
var q,r,q2,s:^byte;
    x1,y1,x2,y2:longint;
    i,j:longint;
begin
x1:=x;
y1:=y;
x2:=x1;
y2:=y1;
q:=p;
inc(q,y*sx+x);
r:=q;   {Q a R nyni ukazuji na start prohledavane oblasti}
while Q^=BARVA_RAMECKU do
      begin inc(q);inc(x2);end;               {mame pravy roh}
q2:=q;          {Q2 se pak vyuzije pri hledani nove bunky}
q:=r; {navrat do vychoziho bodu}
while Q^=BARVA_RAMECKU do
      begin inc(q,sx);inc(y2);end;  {mame dolni roh}


sirka:=x2-x1-1;
vyska:=y2-y1-1;
GetMem(snimek,sirka*vyska);
s:=snimek;
q:=r;
for j:=y1+1 to y2-1 do
    begin
    Move(q^,s^,sirka);
    inc(s,sirka);
    inc(q,sx);
    end;


Následovat bude hledání další buňky a nastavení návratového kódu funkce...
end;  

Koncept je snad jasný. Dokud bude ObrazovaAnalyza vracet 0 nebo 1 tak ji budeme volat stále znovu, abychom načetli všechny fáze ve všech liniích.

Někteří z vás si možná říkají: "Proč jsou animační snímky rozděleny do nějakých linií?"
Vtip je v tom, že každá linie se chápe jako cyklická animace. Důležitou výjimku z tohoto pravidla objasním za chvíli.
Řekněme, že nám hezky cyklicky plápolá oheň. - cyklí nám linie 1.
Pak ale hrdina ve hře nějak uhasí oheň, ten zhasne a dál vychází jen proužek páry. - cyklí nám nějaká jiná linie

Animační linie jsou tedy přepínány událostmi v programu!
Podívejte se teď na zase o něco složitější obrázek:

Napřed si prohlédněte druhou linii. Housenka na ní leze doprava. (Proč mají některé buňky širší okraje si povíme později.) Je to jasný cyklický děj.
Housenka leze tak dlouho, dokud nenarazí na překážku. Když zjistíme, že narazila, tak housenku přepneme na linii 3.
Vidíme, že v linii 3 je rozkresleno obrácení housenky. Ale hlavně, vidíte úplně vpravo tu mrňavou tečku? Ta nám indikuje, že nejde o cyklický děj! a že se má po proběhnutí této linie housenka přepnout na linii následující.
Tohle proběhne automaticky a program se na to nemusí vůbec soustředit!
Podobná situace je u linie 5. Tam je zachycena smrt housenky během lezení doprava. Vpravo je zase kouzelná tečka, která nám zajistí, že housenka nebude hebat znova a znova.

Koncept s tečkou je v Jupírovi ještě rozšířen. Tečku můžeme chápat jako jednopixelovou svislou čárku.
Hm... Jednopixelová čárka. A housenka taky zhebla jednou.
Co kdyby tam byla dvoupixelová svislá čárka? Proběhla by animace smrti housenky dvakrát?
Ve hře Jupír 2 ano.

A proč je v některých animačních snímcích buňka na jedné straně širší?
Takto je definován posun.
Pokud je např. pravá stěna buňky o dva pixely sirší než levá stěna, tak se objekt v tomto snímku posune o dva pixely doprava. Říkám tomu rámečkový posun
Naše housenka je udělaná tak, aby lezla typicky píďalkovitým pohybem. Tedy nestejnoměrný pohyb.
Zase si uvědomte, že program se opět na nic nemusí soustředit. Problematiku pohybu nám neřídí zdrojový kód, ale samotný obrázek!
Chce si to promyslet ještě jednu věc. Co budete dělat, jestli budou dvě následující fáze, tedy následující obrázky, nikoliv šířka rámečku, různě velké. Podívejte se třeba na otáčení housenky.
Některé animační fáze jsou širší než jiné. Třetí fáze je široká, plácnu, 40 pixelů, ale čtvrtá jenom 32. Pátá zase 40.
Jedna možnost je nechat to být a v programu to nijak neošetřit. Pozice objektu se obvyklá udává podle horního levého rohu. Řekněme, že máme animaci, jak se nafukuje balónek. Pokud bychom to nechali neřešeno, tak by to vypadalo tak, že by se při nafukování balónek současně sunul doprava. Použiju výraz, že obrázek roste odleva a odshora
Umíte si to představit? Není to ideální. Samozřejmě máte možnost toto šoupnutí kompenzovat pomocí rámečkového posunu (pokud ho tedy implementujete), ale je otázkou, zda by nebylo vhodnější takovéto situace ošetřit. Je to věc názoru, ale mně příjde lepší si při vykreslování hlídat rozdíly velikostí následujících animačních fází a kompenzačně upravovat polohu objektu tak, aby v ose X vyrůstal odstředu a v ose Y odzdola. Vy se ale můžete přiklonit k růstu v ose Y taky odstředu - to je na vás.


Je zřejmé, že definování animace do jednoho obrázku je velice mocná zbraň. Samozřejmě musíte mít důkladnou obrazovou analýzu, aby uměla všechny tyto vymoženosti detekovat.
V Jupírovi jsou ještě dvě další méně důležitá, ale na implementaci dosti komplikovaná rozšíření: definovaná poloprůhlednost a přeskok na jinou animační linii než LINIE:=LINIE+1
Ale to už je na vás. Tuto metodu je prostě možné dál rozšiřovat.
Až do té doby, dokud budete chápat svoji funkci ObrazovaAnalyza a do té doby, dokud bude formátu rozumět váš grafik...
...což mi připomnělo ještě jednu příjemnou drobnost.
Tím, jak se nakonec použije jen vnitřek buněk, tak mimo buňky můžete hodit co chcete. Samozřejmě byste si měli dát pozor, aby jste nepoužili stejnou barvu jako tu, kterou jsou nakreslené rámečky buněk (Aby to nepletlo obrazovou analýzu). Využít to můžete pro instrukce dalším členům vašeho týmu. Třeba jako v tomto vývojovém konceptu netopýra:


Doufám, že byl pro vás tento text užitečný a že vám nevadilo, že jsem si tu tak trochu honil triko :-)
2006-11-30 | Laaca
Reklamy:
Trávicí enzymy pro podporu zažívání trávení