Gra sterowana kamerą - bieg po farbę

02.07.2017 - Filip Mróz
TrudnośćTrudność

Zużycie farby oraz sterowanie rozgrywką

Wykonajmy pierwszy skok, w którym dodamy mechanizm kończenia się farby i przeplatania się dwóch różnych etapów rozgrywki.

Gotowy kod z tej części można znaleźć w paczce ze źródłami w katalogu: Wersja_3_Etapy

Farba się kończy

Zajmijmy się zużywaniem się farby podczas malowania. Do implementacji tego efektu w StanMalowania będziemy potrzebowali informacji o tym, ile farby jeszcze pozostało. Do wizualizacji jej ilości przyda się nam stała określająca szerokość paska farby:

protected:
    static const int SZEROKOSC_PASKA_FARBY = 40;
    $ \dots $
public:
    double pozostalaFarba;
Stany.h

Następnie zmienimy metodę uaktualnijStan tak, by przed pomalowaniem kawałka płotu sprawdzała ona, czy jeszcze została farba:

if(pozostalaFarba>0) {
    Point polozenie = przetwarzanieObrazow.polozeniePedzla(obraz);
    if(polozenie.x>0) {
        double zuzytaFarba;
StanMalowania.cpp

Chcemy, aby farba zużywała się proporcjonalnie do pomalowanego obszaru. Ustalimy więc ilość zużytej farby w jednym kroku jako ilość "pomalowanych" pikseli:

        // Ile już pomalowaliśmy.
        int ilePomalowanoDotad = cv::countNonZero(_aktualnyStan); 
        // Malujemy.
        circle(_aktualnyStan,polozenie,ROZMIAR_PEDZLA,255,-1);
        bitwise_and(_aktualnyStan, _zarysPlotu, _aktualnyStan);
        // Ile pomalowaliśmy w tym kroku.
        zuzytaFarba = cv::countNonZero(_aktualnyStan) - ilePomalowanoDotad;
        pozostalaFarba -= zuzytaFarba;
        pozostalaFarba = max(0.0,pozostalaFarba); 
    }
}
StanMalowania.cpp

Pozostaje nam poinformowanie gracza o posiadanej ilości farby, co zrobimy w wyswietlStan. Pokażemy na dole ekranu pasek o takiej powierzchni, jaką gracz może jeszcze pomalować. By zwiększyć czytelność kodu malującego prostokąty dodamy do Uzytki metodę rysujProstokat, która namaluje nam prostokąt o podanych wymiarach, umieszczając jego dolny lewy róg w podanym punkcie:

void rysujProstokat(Mat gdzie, Point DLRog, int wysokosc, int dlugosc, Vec3b kolor);
Uzytki.h
void Uzytki::rysujProstokat(Mat gdzie, Point DLRog, int wysokosc, int dlugosc, Vec3b kolor) {
    rectangle(gdzie,DLRog,Point(DLRog.x+dlugosc,DLRog.y-wysokosc),(CvScalar)kolor,-1);
}
Uzytki.cpp

Teraz wykorzystamy tę metodę do wizualizacji ilości pozostałej farby:

        $ \dots $
        gdzie.setTo(kolor,_aktualnyStan);
        // Niech będzie mały odstęp od lewej strony.
        int przesuniecie = 10; 
        // Zapewnijmy, że pasek nie wyjedzie poza plansze.
        int dlugoscPaska = min(gdzie.cols - przesuniecie - 10,(int)(pozostalaFarba / SZEROKOSC_PASKA_FARBY));
        Uzytki::rysujProstokat(gdzie,Point(przesuniecie,gdzie.rows - 10),SZEROKOSC_PASKA_FARBY, dlugoscPaska, kolor);
    }
}
StanMalowania::wyswietlStan

StanGry w lewym dolnym rogu obrazu wyświetla pozostały czas rozgrywki, więc musimy go przemieścić trochę wyżej, aby zrobić miejsce na pasek:

    char pozostalyCzas[20];
    sprintf(pozostalyCzas,"%.2lf s.", _pozostalyCzasGry);
    Uzytki::wypiszTekst(gdzie,pozostalyCzas,Point(10,gdzie.rows-55));
}
StanGry::wyswietlStanGry

Na koniec ustawmy początkową ilość farby w konstruktorze StanMalowania.

pozostalaFarba = 30000;
StanMalowania.cpp

Część dotycząca zużywania się farby podczas malowania jest już gotowa. Możemy uruchomić program i sprawdzić, czy rzeczywiście działa:

Test zużywania się farby podczas malowania

Sterowanie rozgrywką

Zmienimy teraz StanGracza tak, by poprawnie wywoływał metody aktywnego stanu i w razie potrzeby zmieniał aktualny etap z biegu na malowanie i odwrotnie. Zacznijmy od dodania do deklaracji klasy StanBiegu, znacznika aktualnej akcji oraz konstruktora:

class StanGracza {
protected:
    enum Akcja {MALOWANIE, BIEG};
    Akcja aktualnaAkcja;
public:
    string nazwa;
    StanMalowania stanMalowania;
    StanBiegu stanBiegu;
 
    StanGracza();
Stany.h

Dodajmy jeszcze wstępną deklarację StanBiegu. Będzie on musiał mieć dostępne pola z aktualnym położeniem gracza, ilością niesionej farby oraz metody do uaktualniania i wyświetlania jego stanu:

class StanBiegu {
public:
    double niesionaFarba;
    double polozenie;
 
    void uaktualnijStan(double czas, Mat obraz);
    void wyswietlStan(Mat gdzie);
};
Stany.h

Wracając do implementacji StanGracza, ustawimy w jego konstruktorze, że początkowy etap to malowanie (trochę farby ze sobą przynieśliśmy!):

StanGracza::StanGracza() {
    aktualnaAkcja = MALOWANIE;
}
StanGracza.cpp

Poprawmy teraz funkcję wyswietlStanGracza tak, aby wyświetlała stan aktualnej akcji:

void StanGracza::wyswietlStanGracza(Mat gdzie) {
    if(aktualnaAkcja == BIEG) {
        stanBiegu.wyswietlStan(gdzie);
    }
    else {
        stanMalowania.wyswietlStan(gdzie);
    }
    $ \dots $
StanGracza.cpp

Zajmijmy się teraz główną metodą sterującą rozgrywką, czyli uaktualnijStanGracza. Wykonajmy najpierw aktualną akcję, a następnie sprawdźmy, czy nie jest konieczna jej zmiana. Bieg po farbę kończy się w momencie doniesienia farby do płotu (dodajemy tę farbę do już posiadanej), a malowanie – gdy zabraknie farby:

void StanGracza::uaktualnijStanGracza(double czas, Mat obraz) {
    if(aktualnaAkcja == BIEG) {
        stanBiegu.uaktualnijStan(czas,obraz);
        if(stanBiegu.niesionaFarba != 0 && stanBiegu.polozenie == 0) {
            stanMalowania.pozostalaFarba += stanBiegu.niesionaFarba;
            stanBiegu.niesionaFarba = 0;
            aktualnaAkcja = MALOWANIE;
        }
    }
    else {
        stanMalowania.uaktualnijStan(czas,obraz);
        if(stanMalowania.pozostalaFarba == 0) {
            aktualnaAkcja = BIEG;
        }
    }
}
StanGracza.cpp

Mamy już mechanikę przejść między etapami, pozostaje nam uzupełnić ostatni element, czyli StanBiegu.

Szkielet StanBiegu

Na razie zaimplementujemy tylko pewien szkielet klasy StanBiegu bez głównej części, jaką jest interakcja z graczem. Dzięki temu będziemy mogli przetestować kod napisany w tej części. Uzupełnijmy deklarację klasy o kolor farby (używany podczas wyświetlania) oraz kilka stałych opisujących pojemność wiadra, odległość między płotem a farbą oraz maksymalną prędkość gracza:

class StanBiegu {
protected:
    static const int ODLEGLOSC = 100;
    static const int POJEMNOSC_WIADRA = 30000;
    static const int MAKSYMALNA_PREDKOSC = 30;
public:
    Vec3b kolorFarby;
Stany.h

Implementację klasy umieścimy w nowym pliku StanBiegu.cpp i zaczniemy od zmian stanu, czyli uaktualnijStan. Ponieważ jest to tylko szkielet tej klasy i gracz nie ma żadnego wpływu na przebieg tego etapu, jedyną czynnością będzie przesuwanie gracza w stronę farby, a następnie z powrotem (z pełnym wiadrem):

void StanBiegu::uaktualnijStan(double czas, Mat obraz) {
    double predkosc = MAKSYMALNA_PREDKOSC;
    if(niesionaFarba == 0) {
        polozenie += czas * predkosc;
    }
    else {
        polozenie -= czas * predkosc;
    }
    if(polozenie>=ODLEGLOSC) {
        polozenie=ODLEGLOSC;
        niesionaFarba = POJEMNOSC_WIADRA;
    }
    else if(polozenie <= 0) {
        polozenie = 0;
    }
}
StanBiegu.cpp

Przejdźmy do wyświetlenia aktualnego stanu biegu. Na chwilę obecną chcemy tylko pokazać, gdzie jest gracz oraz ile farby niesie. Położenie zaznaczymy na poziomej osi, gdzie lewy kraniec to płot, a prawy – Castorama:

void StanBiegu::wyswietlStan(Mat gdzie) {
    int lewyKraniec = 10;
    int prawyKraniec = gdzie.cols - 20;
    int dlugosc = prawyKraniec - lewyKraniec;
 
    // Czarna oś.
    Uzytki::rysujProstokat(gdzie,Point(lewyKraniec,gdzie.rows - 20),5,dlugosc,0);
    // Lewy koniec osi w kolorze szarym.
    Uzytki::rysujProstokat(gdzie,Point(lewyKraniec,gdzie.rows - 15),15,10,Vec3b(120,120,120));
    // Prawy koniec osi w kolorze farby.
    Uzytki::rysujProstokat(gdzie,Point(prawyKraniec,gdzie.rows - 15),15,10,kolorFarby);
 
    // Zaznaczenie gracza na osi kolorem niebieskim.
    int polozenieGracza = lewyKraniec + polozenie * dlugosc / ODLEGLOSC;
    Uzytki::rysujProstokat(gdzie,Point(polozenieGracza,gdzie.rows - 15),15,10,Vec3b(255,0,0));
StanBiegu.cpp

Ilość farby w niesionym wiadrze oznaczymy paskiem w kolorze farby. Długość paska będzie proporcjonalna do wypełnienia wiadra i będzie zajmować cały ekran, gdy wiadro będzie pełne.

    // Proporcjonalna długość paska (+10 by dorównać paskowi położenia).
    int dlugoscPaskaFarby = niesionaFarba * (dlugosc + 10) / POJEMNOSC_WIADRA;
    Uzytki::rysujProstokat(gdzie,Point(lewyKraniec,gdzie.rows - 35),10,dlugoscPaskaFarby,kolorFarby);
}
StanBiegu.cpp

Gra nam się trochę rozrosła, więc musimy zwiększyć długość rozgrywki w main.cpp tak, aby można było poznać dobrze jej wszystkie elementy:

stanGry.rozpocznijGre(50);
main.cpp
Na koniec ustawmy jeszcze kolor farby w StanGracza:
    stanBiegu.kolorFarby = stanMalowania.kolor;
}
StanGracza::ustawParametryPedzla

Możemy uruchomić grę i sprawdzić, jak aktualnie wygląda rozgrywka:

Gracz przesuwa się wzdłuż osi położenia

0
Twoja ocena: Brak

Copyright © 2008-2010 Wrocławski Portal Informatyczny

design: rafalpolito.com