Pytanie
Kiedy masz grę 2D, która wykorzystuje naprawdę duże obrazy (większe niż sprzęt może obsłużyć), co robisz? Może jest jakieś interesujące rozwiązanie tego problemu, które nigdy wcześniej mi się nie zdarzyło.
W zasadzie pracuję nad rodzajem graficznego twórcy gier przygodowych. Moją grupą docelową są ludzie z niewielkim lub żadnym doświadczeniem w tworzeniu gier (i programowaniu). Jeśli pracujesz z taką aplikacją, czy nie wolałbyś, aby poradziła sobie z tym problemem wewnętrznie, niż nakazując „podzielenie zdjęć”?
Kontekst
Powszechnie wiadomo, że karty graficzne nakładają zestaw ograniczeń dotyczących rozmiaru tekstur, których mogą używać. Na przykład podczas pracy z XNA jesteś ograniczony do maksymalnego rozmiaru tekstury 2048 lub 4096 pikseli w zależności od wybranego profilu.
Zwykle nie stanowi to większego problemu w grach 2D, ponieważ większość z nich ma poziomy, które można zbudować z mniejszych pojedynczych elementów (np. Kafelków lub przetworzonych duszków).
Ale pomyśl o kilku klasycznych graficznych przygodowych punktach i zabawach (np. Monkey Island). Pokoje w graficznych grach przygodowych są zwykle zaprojektowane jako całość, z niewielkimi lub żadnymi powtarzalnymi sekcjami, podobnie jak malarz pracujący nad krajobrazem. A może gra taka jak Final Fantasy 7, która wykorzystuje duże, wstępnie renderowane tła.
Niektóre z tych pokoi mogą stać się dość duże, często obejmując trzy lub więcej ekranów o szerokości. Teraz, jeśli weźmiemy pod uwagę te tła w kontekście nowoczesnej gry w wysokiej rozdzielczości, z łatwością przekroczą maksymalny obsługiwany rozmiar tekstury.
Oczywistym rozwiązaniem tego problemu jest podzielenie tła na mniejsze sekcje, które pasują do wymagań i narysowanie ich obok siebie. Ale czy to ty (lub twój artysta) musisz być tym, który dzieli wszystkie twoje tekstury?
Jeśli pracujesz z interfejsem API wysokiego poziomu, czyż nie powinno być przygotowane na poradzenie sobie z tą sytuacją i wykonanie podziału i składania tekstur wewnętrznie (jako proces wstępny, taki jak potok treści XNA lub w czasie wykonywania)?
Edytować
Oto, o czym myślałem, z punktu widzenia implementacji XNA.
Mój silnik 2D wymaga tylko bardzo małego podzbioru funkcji Texture2D. Naprawdę mogę to zredukować do tego interfejsu:
public interface ITexture
{
int Height { get; }
int Width { get; }
void Draw(SpriteBatch spriteBatch, Vector2 position, Rectangle? source, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects effects, float layerDepth);
}
Jedyną metodą zewnętrzną, której używam, która opiera się na Texture2D, jest SpriteBatch.Draw (), aby móc utworzyć pomost między nimi, dodając metodę rozszerzenia:
public static void Draw(this SpriteBatch spriteBatch, ITexture texture, Vector2 position, Rectangle? sourceRectangle, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects effects, float layerDepth)
{
texture.Draw(spriteBatch, position, sourceRectangle, color, rotation, origin, scale, effects, layerDepth);
}
Pozwoliłoby mi to używać zamiennie ITexture za każdym razem, gdy korzystałem wcześniej z Texture2D.
Następnie mógłbym stworzyć dwie różne implementacje ITexture, np. RegularTexture i LargeTexture, gdzie RegularTexture po prostu owinąłby zwykłą Texture2D.
Z drugiej strony LargeTexture zachowałby Listę Texture2D odpowiadającą jednemu z podzielonych fragmentów pełnego obrazu. Najważniejsze jest to, że wywołanie Draw () na LargeTexture powinno być w stanie zastosować wszystkie transformacje i narysować wszystkie elementy tak, jakby były tylko jednym ciągłym obrazem.
Wreszcie, aby cały proces był przejrzysty, stworzyłbym ujednoliconą klasę modułu ładującego tekstury, który działałby jako metoda fabryczna. Wziąłby ścieżkę tekstury jako parametr i zwróciłby teksturę, która byłaby tekstem regularnym lub tekstem dużym, w zależności od rozmiaru obrazu przekraczającego limit lub nie. Ale użytkownik nie musiał wiedzieć, który to był.
Trochę przesada, czy to brzmi dobrze?
Odpowiedzi:
Myślę, że jesteś na dobrej drodze. Musisz podzielić obrazy na mniejsze części. Ponieważ tworzysz gry, nie możesz korzystać z potoku treści XNA. Chyba że twój twórca bardziej przypomina silnik, który nadal będzie wymagał od programistów korzystania z programu Visual Studio. Musisz więc zbudować własne procedury, które dokonają podziału. Powinieneś nawet rozważyć przesyłanie strumieniowe kawałków, zanim staną się widoczne, aby nadal nie ograniczać ilości pamięci wideo, jaką powinni mieć odtwarzacze.
Istnieje kilka sztuczek, które możesz zrobić specjalnie dla gier przygodowych. Wyobrażam sobie, jak we wszystkich klasycznych grach przygodowych, będziesz mieć kilka warstw tych tekstur, aby ... Twoja postać mogła iść za drzewem lub budynkiem ...
Jeśli chcesz, aby te warstwy miały efekt paralaksacji, wówczas przy dzieleniu płytek po prostu pomiń wszystkie półprzezroczyste jeden raz. Tutaj musisz pomyśleć o wielkości płytek. Mniejsze kafelki dodadzą zarządzanie narzutami i losowaniem połączeń, ale będą mniej danych, w przypadku gdy większe kafelki będą bardziej przyjazne dla GPU, ale będą miały dużo przezierności.
Jeśli nie masz efektu paralaksacji, zamiast pokrywać całą scenę 2-4 gigantycznymi obrazami o głębokości 32 bitów, możesz utworzyć tylko jedną głębokość 32 bitów. I możesz mieć warstwę matrycową lub głęboką, która będzie miała tylko 8 bitów na piksel.
Wiem, że to nie jest łatwe do zarządzania zasobami przez twórców gier, ale tak naprawdę nie musi być. Na przykład, jeśli masz ulicę z 3 drzewami, a gracz idzie za 2 z nich, przed trzecim drzewem i resztą tła ... Po prostu ułóż układ sceny tak, jak chcesz w edytorze, a następnie w krok po kroku od twórcy gry, możesz upiec te gigantyczne obrazy (dobrze małe płytki dużego obrazu).
O tym, jak radziły sobie klasyczne gry. Nie jestem pewien, czy prawdopodobnie zrobili podobne sztuczki, ale musisz pamiętać, że w czasie, gdy ekran miał 320 x 240, gry miały 16 kolorów, co przy użyciu tekstur z palatelizacją pozwala zakodować 2 piksele w jednym bajcie. Ale nie tak działają obecne architektury: D
Powodzenia
źródło
Wytnij je, aby nie były już dowolnie duże, jak mówisz. Nie ma magii. Te stare gry 2D albo to zrobiły, albo były renderowane w oprogramowaniu, w którym to przypadku nie było żadnych ograniczeń sprzętowych poza wielkością pamięci RAM. Warto zauważyć, że wiele z tych gier 2D naprawdę nie miało wielkiego tła, po prostu przewijało się powoli, więc poczuli się ogromnie.
To, co chcesz zrobić w graficznym twórcy gier przygodowych, to wstępne przetworzenie tych dużych obrazów podczas pewnego kroku „kompilacji” lub „zintegrowania”, zanim gra zostanie spakowana do uruchomienia.
Jeśli chcesz użyć istniejącego interfejsu API i biblioteki zamiast pisać własny, sugeruję, aby przekonwertować te duże obrazy na kafelki podczas tej „kompilacji” i użyć silnika kafelków 2D, którego jest wiele.
Jeśli chcesz korzystać z własnych technik 3D, możesz nadal chcieć podzielić na kafelki i użyć dobrze znanego i dobrze zaprojektowanego API 2D do napisania własnej biblioteki 3D, w ten sposób masz pewność, że przynajmniej zaczniesz od dobry projekt API i mniej wymaganego projektu z twojej strony.
źródło
Jeśli jesteś zaznajomiony z wyglądem kwadratu w praktyce wizualnej, zastosowałem metodę, która tnie szczegółowe duże obrazy na mniejsze, względnie podobne do tego, jak wygląda dowód / wizualizacja kwadratu. Jednak moje zostały stworzone w ten sposób, aby specjalnie wyciąć obszary, które można zakodować lub skompresować inaczej w zależności od koloru. Możesz użyć tej metody i zrobić to, co opisałem, ponieważ jest ona również przydatna.
Zmniejszenie ich w czasie wykonywania wymagałoby tymczasowo więcej pamięci i spowodowało spowolnienie czasu ładowania. Powiedziałbym, że zależy to jednak od tego, czy bardziej zależy ci na fizycznym magazynowaniu, czy na czasie ładowania gry użytkownika utworzonej za pomocą edytora.
Czas ładowania / pracy: sposobem, w jaki chciałbym zająć się ładowaniem, jest po prostu pocięcie go na kawałki o proporcjonalnych rozmiarach. Uwzględnij jeden piksel po prawej stronie, jeśli jest to nieparzysta liczba pikseli, nikt nie lubi wycinania ich obrazów, szczególnie niedoświadczeni użytkownicy, którzy mogą nie wiedzieć, że to nie zależy od nich. Uczyń je połową szerokości LUB połową wysokości (lub 3rds, 4ths cokolwiek) powodem, że nie w kwadraty lub 4 sekcje (2 rzędy 2 kolumny) jest to, że zmniejszyłoby pamięć kafelkowania, ponieważ nie martwiłbyś się zarówno x, jak i y w tym samym czasie (i w zależności od tego, ile obrazów jest tak dużych, może to być bardzo ważne)
Przed rozdaniem / automatycznie: W tym przypadku podoba mi się pomysł, aby dać użytkownikowi możliwość przechowywania go na jednym obrazie lub przechowywania go jako oddzielnych elementów (jeden obraz powinien być domyślny). Przechowywanie obrazu jako .gif działałoby świetnie. Obraz znajdowałby się w jednym pliku i zamiast każdej klatki będącej animacją, byłby bardziej jak skompresowane archiwum tego samego obrazu (co w gruncie rzeczy jest w zasadzie .gif). Pozwoliłoby to na łatwość fizycznego transportu plików, łatwość ładowania:
Aha, a jeśli użyjesz metody .gif, powinieneś zmienić rozszerzenie pliku na coś innego (.gif -> .kittens) ze względu na mniej zamieszania, gdy Twój .gif animuje się jak uszkodzony plik. (nie martw się o zgodność z prawem, .gif jest formatem otwartym)
źródło
To zabrzmi oczywisto, ale dlaczego nie podzielić pokoju na mniejsze części? Wybierz dowolny rozmiar, który nie uderzy głowami w żadną kartę graficzną (np. 1024x1024 lub może większą) i po prostu rozbijesz pokoje na kilka części.
Wiem, że pozwala to uniknąć problemu i, szczerze mówiąc, jest to bardzo interesujący problem do rozwiązania. W każdym razie jest to trochę trywialne rozwiązanie, które może trochę ci pomóc.
źródło