Grafický projekt 2

V predchádzajúcej kapitole sme začali s definíciou triedy AnimovanýObjekt. V nej naprogramujeme čo najviac všeobecných vlastností animovaných objektov, ktoré neskôr upravíme pre pasažierov a prievozníka. V rámci tejto kapitoly dokončíme základ triedy AnimovanýObjekt.

Úvodom dokončíme možnosti inicializácie objektu. Predvolené hodnoty všetkých vlastností sú nastavované buď priamo definične, alebo v konštruktore. Doteraz sme poskytli možnosť zapnutia animácie pri chôdzi (pričom sme si zároveň stanovili vlastné vnútorné pravidlá). Ešte budeme potrebovať umožniť nastavenie fyzickej cieľovej pozície objektu na ľavom a pravom brehu. To znamená pozície určujúce pevné body na obrazovke so súradnicami x, y, na ktoré objekt na obrazovke dokráča potom, čo ho pošleme na jeden z brehov. Pre prievozníka to bude stabilná pozícia pri brehu a pre postavičky na brehu. Vyrobíme si na to dvojicu metód:

public void nastavĽavý(double x, double y)
{
    ľavýX = x;
    ľavýY = y;
}
 
public void nastavPravý(double x, double y)
{
    pravýX = x;
    pravýY = y;
}

(Zamyslenie: dokážem si predstaviť, ako by sme ich v rámci iného projektu vedeli využiť v tele cyklu, ktorý by umiestňoval postavičky zo zoznamu, konkrétne Zoznam<AnimovanýObjekt>, v jednom rade, ale pre náš projekt to nebude využiteľné, my budeme mať tri samostatné inštancie, pre ktoré sa neopláca definovať zoznam. Preto budeme nastavovať pozíciu „ručne“ – pri inicializácii jednotlivých inštancií.)

Tým sme uzavreli potreby a možnosti inicializácie animovaných objektov. Ďalej doprogramujeme trojicu jednoduchých metód na zisťovanie stavov objektu. V podstate sa všetky tri budú dotýkať tej istej inštančnej premennej pozícia, to znamená aktuálneho logického umiestnenia objektu (niektorý breh alebo paluba plte). Metódy umožnia rýchle overenie toho, či sa objekt nachádza na niektorom z brehov, alebo zistenie konkrétne definovaného stavu a vďaka tomu budeme môcť stavy porovnávať (budeme to potrebovať – spomeňte si, ako sme pri programovaní textového projektu overovali úspešnosť a neúspešnosť hráčovho počínania):

public boolean pravýBreh()
{
    return pozícia == Pozícia.pravýBreh;
}
 
public boolean ľavýBreh()
{
    return pozícia == Pozícia.ľavýBreh;
}
 
public Pozícia pozícia()
{
    return pozícia;
}

Ďalšou záležitosťou na vyriešenie je posielanie objektov na konkrétne pozície na obrazovke – rozanimovanie objektov. Prvú časť tejto funkcionality sme už naprogramovali v rámci metódy kresliTvar, čo zahŕňalo vykonanie všetkých potrebných definícií. Ďalšie dve časti sa budú dotýkať naprogramovania metód zahajujúcich (odštartujúcich) animáciu a prekrytia metód robota starajúcich sa o tie aktivity robota, ktoré fyzicky zabezpečujú animovanie objektu. Najskôr implementujme metódy slúžiace na posielanie objektov na jednotlivé brehy (čiže na kotviace pozície na brehoch definované pomocou premenných pravýX, pravýY, ľavýXľavýY) a na palubu plte. Všetko potrebné na naprogramovanie dvojice metód, ktoré spustia fyzický presun robota na určený breh už máme k dispozícii. Stačí poslať robota na cieľovú pozíciu, čím dôjde automaticky k jeho aktivácii, a o ostatné sa už postará funkcionalita robota. Popritom zároveň zmeníme hodnotu vnútornej premennej pozícia, čiže logickú pozíciu robota (animovaného objektu):

public void choďNaĽavý()
{
    cieľ(ľavýX, ľavýY);
    pozícia = Pozícia.ľavýBreh;
}
 
public void choďNaPravý()
{
    cieľ(pravýX, pravýY);
    pozícia = Pozícia.pravýBreh;
}

Pokusne vytvorme inštanciu animovaného objektu pre vlka a postupne zavolajme jeho metódy kresliTvar (tú preto, lebo animovaný objekt v čase vytvorenia sveta (vrátane kresliaceho plátna) ešte nemá inicializované všetky premenné a nemôže sa správne vykresliť), umožniAnimáciuChôdze, nastavPravýchoďNaPravý:

AnimovanýObjekt vlk = new AnimovanýObjekt("vlk", 0, 0, 0, 0);
vlk.kresliTvar();
vlk.umožniAnimáciuChôdze();
vlk.nastavPravý(100, 0);
vlk.choďNaPravý();

Príkazy môžeme zadať do anonymného statického bloku prázdnej pokusnej triedy (public class Pokus { static { }}), z ktorej následne vytvoríme inštanciu, alebo vytvoriť inštanciu triedy AnimovanýObjekt v prostredí BlueJ-a – kliknutím pravého tlačidla nad preloženou triedou AnimovanýObjekt a voľbou položky „new…“ a následne spustíme želané metódy nad vytvoreným objektom – kliknutím pravého tlačidla nad (červenou) inštanciou v spodnej časti hlavného okna a voľbou želanej metódy. Prvý spôsob znamená menej klikania.

Uvidíme vlka pohybujúceho sa zo stredu doprava (na súradnicu x = 100 bodov), pričom počas chôdze bude mať stále zdvihnutú len jednu labu. Striedanie fáz animácie zabezpečíme v prekrytej metóde aktivita:

@Override public void aktivita()
{
    if (fázaChôdze != 0 && ++fázaChôdze > fázChôdze) fázaChôdze = 1;
}

Tento zápis zabezpečuje, aby sa hodnota premennej fázaChôdze menila v rozsahu 1fázChôdze, avšak len v prípade, že jej hodnota je nenulová. (Pretože iba vtedy máme istotu, že sú inicializované všetky potrebné premenné.) Ak vytvoríme vlka teraz a vykonáme všetky vyššie uvedené príkazy, uvidíme vlka kráčať. Tým sme zabezpečili, aby sa postavička mohla z ľubovoľného miesta na plátne presunúť na stabilnú pozíciu na brehu a aby bola počas toho animovaná jej činnosť (chôdza). Podobne budeme potrebovať zabezpečiť posielanie postavičiek na palubu plte.

Na to, aby sme vlka (alebo akúkoľvek postavičku) poslali na palubu plte, potrebujeme mať definovaného prievozníka. Ten síce v súčasnosti nejestvuje, ale môžeme si vopred vytvoriť „tieňový“ objekt, s ktorým môžeme narábať bez ohľadu na to, či momentálne jestvuje alebo nie. Inak povedané, môžeme naprogramovať metódy pracujúce s prievozníkom bez toho, že by prievozník jestvoval. Ibaže vyskúšať ich funkčnosť budeme môcť až v ďalšej fáze programovania – v čase, keď vytvoríme skutočného prievozníka.

Definujme statickú vlastnosť prievozník – inicializujeme ju neskôr, v čase vytvorenia skutočného prievozníka. (Ako sme povedali, vlastnosť budeme v súčasnosti chápať ako prievozníkov „tieň“ a prechod na palubu plte si budeme môcť prakticky vyskúšať neskôr, keď bude pltník fyzicky jestvovať.) Vlastnosť prievozník bude ukazovateľom na pltníka, čiže všade, kde budeme potrebovať pracovať s animovaným objektom reprezentujúcim prievozníka, použijeme túto vlastnosť. V súlade s princípom objektovej uzavretosti (encapsulation), bude vlastnosť ukazovateľa na pltníka realizovaná trojicou: súkromná premenná, verejné metódy (jedna na zápis, druhá na čítanie):

private static AnimovanýObjekt prievozník;
 
public static void prievozník(AnimovanýObjekt prievozník)
{
    AnimovanýObjekt.prievozník = prievozník;
}
 
public static AnimovanýObjekt prievozník()
{
    return prievozník;
}

Metóda inicializujúca prechod postavičky na palubu plte bude túto vlastnosť využívať predpokladajúc, že v čase jej spustenia už bude pltník jestvovať:

public void choďNaPrievozníka()
{
    cieľ(cieľ = prievozník);
    pozícia = Pozícia.prievozník;
}

Metóda (okrem zmeny pozície) jedným razom nastaví nový cieľ animovaného objektu (to jest súkromný cieľ animovanej postavičky – to je vlastnosť definovaná v rámci animovaného objektu pre naše potreby) na prievozníka a začne prechádzanie na jeho aktuálnu pozíciu (to jest – zároveň nastaví aktuálny cieľ zdedený po robotovi). Metóda cieľ (pozri dokumentáciu triedy GRobot) je schopná začať presun smerom na cieľový objekt (napr. prievozníka), ale iba na jeho aktuálnu polohu v čase jej spustenia. Ak by sa počas presunu postavičky na palubu pltník pohol, postavička by dorazila na pozíciu, kde bol pltník v čase začatia svojho pohybu, a tam by zastavila. Ak chceme túto nelogickosť vyriešiť, pridáme do metódy aktivita pravidelnú aktualizáciu cieľa:

@Override public void aktivita()
{
    if (null != cieľ) upravCieľ(cieľ);
    if (fázaChôdze != 0 && ++fázaChôdze > fázChôdze) fázaChôdze = 1;
}

Teraz by malo byť všetko v poriadku, testovaním by sme však rýchlo zistili zvláštne správanie. Konkrétne, keby sme chceli postavičku poslať na ľubovoľné súradnice na plátne (to jest, realizovať vystúpenie postavičky z plte na breh), mala by stále tendenciu zostať na palube plte, pretože jej cieľ by bol stále aktualizovaný smerom na pltníka. Stalo by sa to hneď na prvom riadku metódy aktivita hovoriacom: „ak je môj súkromný cieľ neprázdny, uprav aktuálny cieľ na tento súkromný cieľ.“ Tento riadok je v poriadku, iba sa musíme postarať o to, aby sa nevykonal vtedy, keď nechceme. Postačí zabezpečiť to, aby sa súkromný cieľ vždy po jeho dosiahnutí vynuloval. To môžeme urobiť prekrytím metódy dosiahnutieCieľa obsahujúcej jediný riadok kódu:

@Override public void dosiahnutieCieľa()
{
    cieľ = null;
}

Teraz sme schopní poslať ľubovoľný animovaný objekt na palubu plte prievozníka (ktorého musíme vopred zvoliť) a na jeden z brehov. O animáciu sa čiastočne starajú mechanizmy robota a čiastočne nami naprogramované vlastnosti triedy AnimovanýObjekt. Napohľad by sme mohli povedať, že trieda AnimovanýObjekt je hotová. Avšak nepredbiehajme, je možné, že počas programovania odvodených tried – PrievozníkPasažier – vzniknú ďalšie požiadavky, ktoré vo fáze plánovania projektu nie sme vždy schopní predvídať…

Príloha 9 –druhá fáza tvorby triedy AnimovanýObjekt

Zobraziť | Prevziať