Wczytywanie dużych bitmap oraz stworzenie własnego systemu okienek

1

Cześć,
naszło mnie ostatnio żeby zrobić własną mini gierkę. Wygląda to już całkiem fajnie. Piszę grę wyścigową coś w stylu F1 (screen w załączeniu).
Mam jednak 2 dylematy:

1) Cała mapa jest w postaci jednej sporej bitmapy. Podczas jej wczytywania otrzymuję błąd "out off memory". Robię to w ten sposób:

track := TBitmap.Create;
track.PixelFormat := pf24bit;
track.LoadFromFile('trasa.bmp');
track.width := 13000;
track.height:= 7000;

Czy możecie podpowiedzieć mi co jeszcze zrobić żeby móc wczytywać "dowolnie" duże obrazy?

2) Sam wyścig mam praktycznie ogarnięty. Dotarłem do momentu gdzie muszę stworzyć całe GUI. Wszystko rysuję na komponencie stworzonym w taki sposób:

type
  TMyDrawingControl = class(TCustomControl)
  public
    procedure EraseBackground(DC: HDC); override;
    procedure Paint; override;
  end;

Czy jedynym rozwiązaniem żeby rysować samodzielnie okna jest stworzenie od zera całego managera okien ich budowania, ogarniania eventów itp.? Czy istnieje może już jakieś gotowe rozwiązanie do takich zabaw?
A może wygodniej będzie stworzyć zwykłe formy, kolorować je i wyświetlać użytkownikowi?

Proszę o rady :)racingScreen.png

5
karpov napisał(a):

1) Cała mapa jest w postaci jednej sporej bitmapy. Podczas jej wczytywania otrzymuję błąd "out off memory". Robię to w ten sposób:

Zacznijmy od samego początku — co to znaczy „sporej”? Bo o ile nie masz 128MB RAM, to nie powinieneś mieć żadnego problemu z samym jej załadowaniem do pamięci. Podaj konkretne informacje, żeby było dokładnie wiadomo na czym stoimy.

Czy możecie podpowiedzieć mi co jeszcze zrobić żeby móc wczytywać "dowolnie" duże obrazy?

Dowolnie dużych nie da rady — zawsze limitem będzie ilość dostępnej pamięci. Co najwyżej możesz pójść w stronę retro-sposobów, czyli zamiast jednej bitmapy tła, użyć dziesiątek/setek małych kafli, z których cała mapa będzie budowana. Wtedy mapa to będzie po prostu dwuwymiarowa macierz indeksów kafli (tekstur), co jest bardzo proste do zaprogramowania oraz drastycznie zmniejsza zapotrzebowanie na pamięć. Im bardziej niepowtarzalne elementy mapy, tym więcej kafli.

Nie wiem jakich klas używasz do obsługi bitmap, dlatego napisz dokładnie co jest Twoim obecnym API.

2) Sam wyścig mam praktycznie ogarnięty. Dotarłem do momentu gdzie muszę stworzyć całe GUI. Wszystko rysuję na komponencie stworzonym w taki sposób:

Czyli Twoim API jest LCL — no nie za dobrze. Obstawiam, że będziesz miał spore problemy z wydajnością (szczególnie na uniksach, bo nie mają GDI+, a LCL używa go domyślnie pod Windows i daje to niemałe przyspieszenie), albowiem renderowanie grafiki przez CPU jest cholernie powolne i nijak nie umywa się do zdolności GPU. Tak więc zanim zaczniesz robić kolejne kroki, zmień samo podejście (czytaj niżej).

Czy jedynym rozwiązaniem żeby rysować samodzielnie okna jest stworzenie od zera całego managera okien ich budowania, ogarniania eventów itp.?

Nie, ale możesz tak robić, jeśli lubisz klepać tysiące linijek kodu w nieskończoność. Tzn. jeśli masz konkretny powód do własnej implementacji całego ekosystemu to go sobie stwórz, ale jeśli nie, to lepiej tego nie rób, bo to pochłonie mnóstwo czasu.

Czy istnieje może już jakieś gotowe rozwiązanie do takich zabaw?

Oczywiście, że istnieją. Masz do dyspozycji takie biblioteki jak Allegro, PasSFML czy potężny ZenGL, które zawierają wszystko czego potrzebujesz do stworzenia pełnoprawnej gry, a przede wszystkim wsparcie DirectX/OpenGL.

Oczywiście tych API/silników jest znacznie więcej. Wszystkie mają bindingsy dla FPC lub są napisane we Free Pascalu (jak ZenGL). Możesz też skorzystać z SDL — też są bindingsy dla FPC. Tej biblioteki użyto do stworzenia m.in. gry Braid — nie w mąkę pierdnął.

2

Sprawdziłem na szybko u siebie i u mnie bmp o takim rozmiarze jaki masz podane z takim samym pixel formatem (260MB) wczytuje się bez żadnego błędu. Może jakaś inna aplikacja (np. przeglądarka) zjadła Ci cały dostępny RAM? Ja mimo wszystko podzieliłbym mapę na mniejsze części. W ogóle tło bym przechowywał jako jpg, a sprite'y jako png. Wiem, że nikt w tych czasach się już nie przejmuje wielkościami plików, ale jakoś do bmp czuję taki jakiś niesmak ;)
Nie wiem jak bardzo złożone chcesz zrobić GUI, ale oprogramowanie kilku obrazków jako przyciski menu itp. nie powinno zająć dużo czasu nawet robiąc to od podstaw.

3
Arthan napisał(a):

W ogóle tło bym przechowywał jako jpg, a sprite'y jako png. Wiem, że nikt w tych czasach się już nie przejmuje wielkościami plików, ale jakoś do bmp czuję taki jakiś niesmak ;)

Z tym wiąże się pewna właściwość mechaniki klas obrazów, jeśli chodzi o LCL (a to jest obecnie bazowym API).

Nie wiem jak w przypadku JPG (bo tego dziadostwa nie używam nigdzie), ale PNG, po załadowaniu do pamięci, nadal jest skompresowany i zajmuje niewiele pamięci. Aby go dekompresować, należy go po prostu użyć — coś na nim namalować, albo namalować go na innym obrazie. Podczas takiego pierwszego użycia, dane obrazu zostają odkodowane i zostaje zaalokowana pełna ilość pamięci (czyli tyle, ile dla zwykłej bitmapy o takim samym rozmiarze).

W pewnych zastosowaniach jest to istotne, dlatego że dekompresja obrazu trochę trwa, dlatego pierwsze wykorzystanie obrazu PNG powoduje lag, a kolejne już nie. Natknąłem się na to przez przypadek, gdy pracowałem nad projektem CTCT. W nim, tła dla scen to obrazy PNG o rozmiarze 1280×720, po zmianie sceny, odpowiedni obraz jest malowany na płótnie okna. I zawsze pierwsza zmiana sceny odmalowywała okno ze sporym opóźnieniem (rzędu 500ms), przez co nie zgrywało się to z timingiem przejść.

Problem rozwiązałem w ten sposób, że nadpisałem metodę LoadFromFile, w której tuż po załadowaniu obrazu z pliku, malowałem go na tymczasowej bitmapie, co wymuszało dekompresję. Program startował nieco dłużej, jednak później każdorazowa aktualizacja płótna okna trwała dokładnie tyle samo i nie miałem problemu z synchronizacją przejść.

Taka mała ciekawostka.

Nie wiem jak bardzo złożone chcesz zrobić GUI, ale oprogramowanie kilku obrazków jako przyciski menu itp. nie powinno zająć dużo czasu nawet robiąc to od podstaw.

Menu nie jest jakoś szczególnie trudne do zaprogramowania — tym bardziej, jeśli ma być obsługiwane tylko z poziomu klawiatury. To też zależy od tego, jakie elementy ma posiadać, bo czym innym jest oprogramowanie pozycji niezmiennych, a czym innym pozycji pozwalających coś wybrać (gdy np. strzałki góra/dół służą do poruszania się po menu, a strzałki lewo/prawo do zmiany wartości konkretnych opcji), a jeszcze czym innym obsługa menu drzewiastych, widocznych po kilka jednocześnie (w postaci małych okienek).

Jeśli potrzebne jest dość zaawansowane menu, to co nieco można podpatrzeć z tego filmiku:

Natomiast jeśli chodzi o dużo prostsze menu gry, bez scrollowania itd., to polecam poczekać dzień/dwa, bo właśnie takie implementuję w swojej gierce i jeśli ktoś jest chętny, to mogę opublikować źródła do wglądu (pełne źródła i tak będą dostępne na GitHub, ale dopiero jak skończę cały projekt). ;)

screenshot-20210602142811.png

0

Dzięki chłopaki za Wasze odpowiedzi. Na Was zawsze można liczyc :)

Przepraszam za opóźnienie z odpowiedzią ale kupa roboty ostatnio i nie mam czasu usiąść nad swoim projektem.

W pierwszej kolejności przepiszę projekt na jakąś bibliotekę, którą zaproponował @furious programming. Sprawdzę jakiego kopa fpsów wszystko dostanie. Pomyślę potem nad podzieleniem mapy na jakieś mniejsze fragmenty.

Co do okien to zauważyłem, że nawet nowoczesne gry aktualnie rezygnują z myszki. Np. w F1 2020 wszystko obsługuje się klawiaturą, każde okno, zakładkę, ustawienia zmienia się strzałkami na klawiaturze i przyciskami F1, F2 itp.

Moja gierka ma być czymś na kształt managera więc zastanawiam się czy to zdałoby to egzamin i czy nie będzie zbyt uciążliwe w obsłudze. W pierwszej wersji nie chce sobie utrudniać za bardzo i może zrobię obsługę klawiaturą i potem pomyślimy. W końcu jestem zdesperowany żeby to był pierwszy projekt który ukończę ;)

1
karpov napisał(a):

W pierwszej kolejności przepiszę projekt na jakąś bibliotekę, którą zaproponował @furious programming. Sprawdzę jakiego kopa fpsów wszystko dostanie.

Powiem tak — obecnie klepię sobie kolejne narzędzie tetrisowe (w poprzednim poście podałem kilka prototypów). Tylny bufor ma rozmiar 256×240 pikseli, renderuję na nim tło i inne małe pierdy, a następnie wyświetlam w oknie, na pełen ekran. Obecnie w ten sposób jestem w stanie przetworzyć około 120-140 klatek na sekundę, na 8-letnim, biznesowym laptopie. Gdybym renderowanie przerzucił na GPU, dostałbym co najmniej 5.000 klatek na sekundę, a na współczesnym pececie, co najmniej kilkanaście tysięcy fps.

O ile nie zwalisz logiki, to wróżę co najmniej kilkaset fps w gotowym produkcie. ;)

Co do okien to zauważyłem, że nawet nowoczesne gry aktualnie rezygnują z myszki.

Obsługa menu z poziomu muszy jest istotna w trzech przypadkach, czyli gdy:

  • głównym urządzeniem sterującym w rozgrywce jest mysz,
  • menu są na tyle skomplikowane/nietypowe, że obsługa z poziomu klawiatury/kontrolera trwałaby wieki i byłaby niewygodna,
  • menu mają dużo opcji i ich przeznaczenie powinno zapewniać szybką zmianę konfiguracji.

Jeśli rozgrywka nie potrzebuje myszy (bo gra się klawiaturą lub kontrolerem), a menu nie mają miliardów opcji (jak w strategiach czasu rzeczywistego), to obsługa myszy w ogóle nie jest potrzebna i nie ma nawet sensu jej implementować. Tak więc zastanów się nad powyższymi punktami i sam zdecyduj, czy jest Ci to w ogóle potrzebne.

1

@furious programming: tego faceta z filmiku, który załączyłeś to znam. Często oglądam jego filmy. Akurat tego nie widziałem i super, że zrobił tam obsługę okien za pomocą myszki. Kiedyś to zaimplementuje ale chyba na razie ograniczę się do klawiatury dla ułatwienia.

Zacząłem przepisywać program. Wybrałem SDL. Na początku miałem problem z wczytywaniem mapy bo dostawałem błąd "Texture dimensions are limited to 8192x8192". Doczytałem gdzieś, że karta graficzna ma ograniczenia co do wielkości tekstur.

W końcu jednak udało mi się wymusić używanie sprzętowej akceleracji oraz korzystanie z opengl. Dzięki temu mogę wczytać dużą mapę i ją wyświetlić.

Jak tylko skończę to dam znać jak poprawiła się liczba fpsów :) Sam jestem mega ciekawy :)

1
karpov napisał(a):

Zacząłem przepisywać program. Wybrałem SDL. Na początku miałem problem z wczytywaniem mapy bo dostawałem błąd "Texture dimensions are limited to 8192x8192". Doczytałem gdzieś, że karta graficzna ma ograniczenia co do wielkości tekstur.

I nadal nie napisałeś, jaka jest obecna rozdzielczość tej mapy. Boisz się o tym napisać? Przecież nie ma się czego wstydzić. ;)

W końcu jednak udało mi się wymusić używanie sprzętowej akceleracji oraz korzystanie z opengl.

No i elegancko. SDL i inne API nie są jakoś szczególnie łatwe w użyciu, ale tutoriali (również dla Pascali) nie brakuje.

0

@furious programming: hehe umknelo mi, mapa ma rozdzielczość 13000x7000. W sumie nie jest jakoś nadzwyczajnie wielka ale dostatecznie żeby juz sprawić kłopoty :)
Na szczęście póki co problem rozwiązany. Zrobię z ciekawości test czy uda się załadować np dwa razy wieksza.

1

Update z placu boju. Cały czas przepisuję apkę. W sumie nową obsługę grafiki mam już zrobioną, muszę tylko przystosować kod. Pod spodem nie ma jeszcze logiki, wyświetlane jest tylko tło ale...... wow..... just wow ;)
fps.PNG

Zajmie mi to trochę dłużej bo chcę posprzątać stary kod :)

Wziąłem się też za obsługę okien. Uświadomiłem sobie, że przecież nie trzeba okien przesuwać itp. a jedynie ustalić im konkretne miejsce i "przypiąć".

Napisałem więc prostego managera okien. Wczytuje plik json gdzie jest ustalona cała struktura okien. Każde okno ma własne tło oraz listę itemsów będących buttonami. Każdy button może przyjąć dwa stany, albo zwykłu albo onHover oraz ma przypisaną pozycję względem swojego parenta. Póki co wygląda to świetnie :) Muszę dorobić jeszcze klikanie buttonów i wyzwalanie w związku z tym akcji np. włączenie nowego okna/zakładki.

Każdy item będzie w przyszłości mógł być czymś innym niż buttonem np. polem tekstowym etc. Poniżej przykładowy json i filmik z działaniem.

{
    "numberOfWindows":1,
    "okno0": {
        "ID" : 0,
        "size":{
            "width":400,
            "height":200
        },
        "position":{
            "x":50,
            "y":50
        },
        "bg":"windows/okno1/windowBG.png",
        "canClose":false,
        "itemsCount":1,
        "items":{
            "0":{
                "type":"button",
                "bg":"windows/okno1/button0.png",
                "onHoverBg":"windows/okno1/button0Hover.png",
                "onClickType":"goToWindow",
                "onClickID":2,
                "size":{
                    "width":150,
                    "height":50
                },
                "position":{
                    "x":40,
                    "y":40
                }
            }
        }
    }
}

1 użytkowników online, w tym zalogowanych: 0, gości: 1, botów: 0