Czyli moje zmagania z Qt i generowaniem terenu. Czym w ogóle jest KMG? Z nazwy można wywnioskować że generuje mapy, jednak po co? Do czego? Więc do rzeczy:
KMG jest moim generatorem map do OTS napisanym w Qt, do prostego generowania terenu. Póki co pozwala stworzyć teren za pomocą 2 metod:
1. Tworzenie terenu na podstawie bitmapy
Już nazwa sugeruje o co chodzi – wrzucamy obrazek, otrzymujemy gotowy teren. Jak? Bardzo prosto: mapa ma taki sam rozmiar jak obrazek (pixele -> kratki), czy w danym miejscu ma być teren czy nie, decyduje prosty warunek gdzie
to zmienna możliwa do zdefiniowania przez użytkownika a
to oczywiście składowe koloru. W uproszczeniu można powiedzieć że zamieniamy obrazek na czarno-biały i sprawdzamy czy jasność jest mniejsza od ustawionej granicy, jeśli tak to w tym miejscu będzie kratka z terenem, jeśli nie – nie będzie nic. Prawda że proste? I mamy np. coś takiego:
2. Generowanie terenu
Tutaj sprawa jest trochę bardziej złożona. Bo jak nie mając nic zrobić fajną, kanciastą wyspę? W poprzedniej wersji KMG robiłem to bazując na punktach wyznaczonych przez użytkownika, no ale to było `a fe`, ma być przecież automatyczne. No dobra, ale jak to zrobić? Na googlu nie znalazłem nic ciekawego więc pozostało mi opracować sposób samemu. Zrobiłem tak:
- Dzielę powierzchnie mapy (o rozmiarach podanych przez użytkownika) na kratki (ilość także podana przez użytkownika)
- W każdej kratce sąsiadującej z krawędzią znajduje punkt o losowych współrzędnych i dodaję go do wektora, dajmy na to punkty
- Mam już
punktów (przy standardowych ustawieniach 12) które tworzą bazową obwódkę mojego terenu
- Między każdą parą punktów dodaję jeszcze jeden czyli między punkty[0] i punkty[1], punkty[1] i punkty[2] … punkty[n-2] i punkty[n -1] i na końcu między punkty[n] i punkty[0] w następujący sposób:
- Znajduję środkowy punkt na odcinku punkty[n-2],punkty[n-1]
– Przesuwam ten punkt prostopadle do odcinka o losową wartość z zakresugdzie
jest wartością zdefiniowaną przez użytkownika. Mam już punkt i dodaję go do wektora na odpowiedniej pozycji
- Powtarzam punkt 4 zdefiniowaną przez użytkownika ilość razy
- Łączę punkty liniami jednocześnie nanosząc je na mapę jako podłoże
- Wypełniam mapę
Chyba łatwe do pojęcia. Niestety działa dość chaotycznie, czasami generując tak fikuśne wynalazki że aż chce się wyłączyć program. Mówię o „krzyżowaniu się” punktów, powstaje wtedy tzw. nieestetyczna ósemka w której na dodatek wypełnianie nie działa. Póki co nie mam pojęcia co z tym fantem zrobić.
Generowanie wody i wypełnianie
Najwolniejsza część. Wodę generuję najprostszym możliwym algorytmem (nic innego nie przychodzi mi do głowy a nie skupiałem się na tym) – po prostu iteruję po każdej klatce, jeśli jest pusta to sprawdzam czy w zasięgu „widzialności” jest jakaś nie pusta kratka. Jeśli jest wstawiam wodę, jeśli nie to lecimy do następnej. Proste, wolne ale działa ;d
Gorzej z wypełnianiem. Korzystam z rekurencyjnego algorytmu zerżniętego z Wikipedii który potrafi się wysypać gdyż nie miałem czasu na szukanie i implementację niczego lepszego. Będę musiał nad tym posiedzieć. Jeśli ktoś ma jakieś pomysły odnośnie wypełniania czy generowania wody to chętnie skorzystam, zaoszczędzi mi to czasu.
Qt, wątki i…
…system slotów i sygnałów. Cały program pisałem w Qt którego jestem fanatykiem. Na początku bez użycia wątków, jednak podczas generowania (które czasem trwało kilka minut) program po prostu się wieszał. Postanowiłem wyrzucić właśnie generowanie do oddzielnego wątku. Przydało by się także jakoś zablokować program żeby użytkownik nie klikał sobie „generuj”, nie bawił się starą mapą, nie próbował zapisać, a przede wszystkim żeby było wiadomo że coś się dzieje. No nic, logiczne było wyświetlenie jakiegoś dialogu z progressbarem więc tak zrobiłem. Qt przychodzi nam tutaj z pomocą gdyż mamy gotową klasę QProgressDialog. Nic prostszego, tworze sobie dialog, nowy wątek, do funkcji generującej przekazuję wskaźnik na dialog aby mogła zmieniać postęp i dzia… nie działa. Dostaję piękny komunikat: „Cannot send events to objects owned by a different thread”. Okazuje się że QProcessDialog nie jest Thread-Safe i nie można wysyłać eventów (a setValue najwyraźniej nim jest) z wątków innych niż ten w którym obiekt został utworzony. No nic, idąc logiczną drogą próbuję stworzyć dialog już w wątku w którym generuję mapę. Gdzie tam, dowiaduje się że musi być stworzony w tym samym wątku co reszta GUI. No to Google w ruch i znajduję inne rozwiązanie tego problemu – Qtowski mechanizm slotów i sygnałów. W uproszczeniu mamy sloty (które poniekąd są funkcjami) i sygnały, które możemy łączyć. Gdy jakiś obiekt wyśle sygnał to odpala się każdy połączony do niego slot.
W klasie w której generuję mapę (dziedziczącej po QThread) tworzę nowe potrzebne sygnały:
signals:
void valueChanged(int value);
void resetProgress();
void setLabelText(const QString &text);
Potem w głównej klasie łączę wszystko:
connect(generator,SIGNAL(valueChanged(int)),progressDialog,SLOT(setValue(int))); connect(generator,SIGNAL(resetProgress()),progressDialog,SLOT(reset())); connect(generator,SIGNAL(setLabelText(QString)),progressDialog,SLOT(setLabelText(QString)));
I tyle. Teraz gdy z mojego wątku wyślę sygnał ( emit valueChanged(i) ) trafi on do odpowiedniego slotu w progressDialogu który ładnie zmieni wartość. Na początku wydawało mi się trochę dziwne ale idzie się przyzwyczaić.
Zapis
Zapisuję wszystko oczywiście w OTBM (Open Tibia Binary Map). Miałem trochę technicznych problemów, ale to przez błąd w liczeniu i brak komentarzy (wyjątkowo dokuczliwe jest gdy kiedyś niby niepotrzebne komentarze mogły by nagle rozjaśnić ówcześnie oczywisty kod) który już poprawiłem.
No i na tym koniec, na kolejną wersję będzie trzeba poczekać.
Download: http://otsoft.pl/?frontpage=download&p=286
No i obiecane pozdro dla Budynia :)


Pięknie kiruś, pięknie.