Więc tworzę RPG HTML5 dla zabawy. Mapa jest <canvas>
(szerokość 512 pikseli, wysokość 352 pikseli | 16 płytek w poprzek, 11 płytek od góry do dołu). Chcę wiedzieć, czy istnieje bardziej skuteczny sposób na pomalowanie <canvas>
.
Oto jak mam to teraz.
Jak płytki są ładowane i malowane na mapie
Mapa jest malowana kafelkami (32x32) za pomocą tego Image()
kawałka. Pliki obrazów są ładowane przez prostą for
pętlę i umieszczane w tablicy wywoływanej tiles[]
jako MALOWANE przy użyciu drawImage()
.
Najpierw ładujemy kafelki ...
a oto jak to się robi:
// SET UP THE & DRAW THE MAP TILES
tiles = [];
var loadedImagesCount = 0;
for (x = 0; x <= NUM_OF_TILES; x++) {
var imageObj = new Image(); // new instance for each image
imageObj.src = "js/tiles/t" + x + ".png";
imageObj.onload = function () {
console.log("Added tile ... " + loadedImagesCount);
loadedImagesCount++;
if (loadedImagesCount == NUM_OF_TILES) {
// Onces all tiles are loaded ...
// We paint the map
for (y = 0; y <= 15; y++) {
for (x = 0; x <= 10; x++) {
theX = x * 32;
theY = y * 32;
context.drawImage(tiles[5], theY, theX, 32, 32);
}
}
}
};
tiles.push(imageObj);
}
Oczywiście, gdy gracz rozpoczyna grę, ładuje mapę, którą ostatnio przerwał. Ale tutaj jest to mapa z trawą.
W tej chwili mapy używają tablic 2D. Oto przykładowa mapa.
[[4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 1, 1, 1, 1],
[1, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 1],
[13, 13, 13, 13, 1, 1, 1, 1, 13, 13, 13, 13, 13, 13, 13, 1],
[13, 13, 13, 13, 1, 13, 13, 1, 13, 13, 13, 13, 13, 13, 13, 1],
[13, 13, 13, 13, 1, 13, 13, 1, 13, 13, 13, 13, 13, 13, 13, 1],
[13, 13, 13, 13, 1, 13, 13, 1, 13, 13, 13, 13, 13, 13, 13, 1],
[13, 13, 13, 13, 1, 1, 1, 1, 13, 13, 13, 13, 13, 13, 13, 1],
[13, 13, 13, 13, 13, 13, 13, 1, 13, 13, 13, 13, 13, 13, 13, 1],
[13, 13, 13, 13, 13, 11, 11, 11, 13, 13, 13, 13, 13, 13, 13, 1],
[13, 13, 13, 1, 1, 1, 1, 1, 1, 1, 13, 13, 13, 13, 13, 1],
[1, 1, 1, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 1, 1, 1]];
Dostaję różne mapy za pomocą prostej if
struktury. Gdy powyższa tablica 2d będzie return
, odpowiednia liczba w każdej tablicy zostanie pomalowana zgodnie z Image()
zapisem w środku tile[]
. Potem drawImage()
nastąpi i pomaluj zgodnie z x
i y
i 32
pomaluj na właściwej x-y
współrzędnej.
Jak występuje przełączanie wielu map
Z mojej gry, mapy mają pięć rzeczy do śledzenia: currentID
, leftID
, rightID
, upID
, i bottomID
.
- currentID: bieżący identyfikator mapy, na której jesteś.
- leftID: Jaki identyfikator
currentID
załadować po wyjściu z lewej strony bieżącej mapy. - rightID: Jaki identyfikator
currentID
załadować po wyjściu z prawej strony bieżącej mapy. - downID: Jaki identyfikator
currentID
załadować po wyjściu z dolnej części bieżącej mapy. - upID: Jaki identyfikator
currentID
załadować po wyjściu na górę bieżącej mapy.
Coś do uwaga: Jeśli któraś leftID
, rightID
, upID
, lub bottomID
nie są specyficzne, co oznacza, że są one 0
. Oznacza to, że nie mogą opuścić tej strony mapy. To tylko niewidzialna blokada.
Tak więc, gdy dana osoba wyjdzie z boku mapy, w zależności od tego, gdzie wyszła ... na przykład jeśli wyszła na dole, bottomID
liczba map
ładowanych osób zostanie w ten sposób namalowana na mapie.
Oto reprezentatywny plik .GIF, który pomoże ci lepiej wizualizować:
Jak widać, wcześniej czy później na wielu mapach będę miał do czynienia z wieloma identyfikatorami. A to może być trochę mylące i nerwowe.
Oczywiste zalety to to, że ładuje 176 kafelków jednocześnie, odświeża małe płótno 512x352 i obsługuje jedną mapę na raz. Wadą jest to, że identyfikatory MAP, gdy mamy do czynienia z wieloma mapami, mogą czasami być mylące.
Moje pytanie
- Czy to skuteczny sposób przechowywania map (biorąc pod uwagę użycie kafelków), czy jest lepszy sposób obsługi map?
Myślałem o wielkiej mapie. Rozmiar mapy jest duży i składa się z jednej tablicy 2D. Jednak rzutnia ma wciąż 512 x 352 pikseli.
Oto kolejny plik .gif, który zrobiłem (na to pytanie), aby pomóc w wizualizacji:
Przepraszam, jeśli nie rozumiesz mojego angielskiego. Proszę pytać o wszystko, co masz problemy ze zrozumieniem. Mam nadzieję, że wyjaśniłem to. Dzięki.
Odpowiedzi:
Edycja: Właśnie zobaczyłem, że moja odpowiedź była oparta na twoim kodzie, ale tak naprawdę nie odpowiedziała na twoje pytanie. Zachowałem starą odpowiedź na wypadek, gdybyś mógł skorzystać z tych informacji.
Edycja 2: Naprawiłem 2 problemy z oryginalnym kodem: - Dodanie +1 w obliczeniach końca x i y było przez pomyłkę w nawiasach, ale należy je dodać po podziale. - Zapomniałem zweryfikować wartości xiy.
Możesz po prostu mieć aktualną mapę w pamięci i załadować otaczające ją mapy. Wyobraź sobie świat, który składa się z 2d tablicy pojedynczych poziomów o rozmiarze 5x5. Gracz zaczyna w polu 1. Ponieważ górna i lewa granica poziomu znajdują się na skraju świata, nie trzeba ich ładować. W takim przypadku poziom 1/1 jest aktywny, a poziomy 1/2 i 2/1 są ładowane ... Jeśli gracz przesunie się teraz w prawo, wszystkie poziomy (oprócz tego, do którego się przeprowadzasz) są rozładowywane, a nowe otoczenie jest załadowany. Oznacza, że poziom 2/1 jest teraz aktywny, 1/1, 2/2 i 3/1 są teraz załadowane.
Mam nadzieję, że daje to wyobrażenie o tym, jak można to zrobić. Ale to podejście nie zadziała bardzo dobrze, gdy każdy poziom musi być symulowany podczas gry. Ale jeśli możesz zamrozić nieużywane poziomy, powinno to działać dobrze.
Stara odpowiedź:
Podczas renderowania poziomu (również na podstawie kafelków) robię to, aby obliczyć, które elementy z tablicy kafelków przecinają się z rzutnią, a następnie renderuję tylko te kafelki. W ten sposób możesz mieć duże mapy, ale wystarczy wyrenderować fragment na ekranie.
Uwaga: powyższy kod nie jest testowany, ale powinien dać wyobrażenie o tym, co robić.
Po podstawowym przykładzie reprezentacji rzutni:
Tak wyglądałaby reprezentacja javascript
Jeśli weźmiesz mój przykład kodu, wystarczy zastąpić deklaracje int i float var. Upewnij się także, że używasz Math.floor do tych obliczeń, które są przypisane do wartości opartych na int, aby uzyskać takie samo zachowanie.
Jedną rzeczą, którą powinienem rozważyć (choć nie jestem pewien, czy to ma znaczenie w javascript), jest umieszczenie wszystkich kafelków (lub jak największej ich liczby) w jednej dużej teksturze zamiast używania wielu pojedynczych plików.
Oto link wyjaśniający, jak to zrobić: http://thiscouldbebetter.wordpress.com/2012/02/25/slicing-an-image-into-tiles-in-javascript/
źródło
640, 480
... ale to może działać z512, 352
prawdą?Przechowywanie map jako kafelków jest przydatne, aby umożliwić ponowne wykorzystanie zasobów i ponowne zaprojektowanie mapy. Realistycznie jednak nie zapewnia tak wiele korzyści graczowi. Ponadto za każdym razem rysujesz ponownie każdą płytkę. Oto co bym zrobił:
Przechowuj całą mapę jako jedną dużą tablicę. Nie ma sensu mieć map i submaps, a także potencjalnie pod-map (np. Jeśli wchodzisz do budynków). W każdym razie ładujesz to wszystko, dzięki czemu wszystko jest ładne i proste.
Renderuj mapę naraz. Przygotuj płótno poza ekranem, na którym renderujesz mapę, a następnie wykorzystaj je w swojej grze. Ta strona pokazuje, jak to zrobić za pomocą prostej funkcji javascript:
Zakłada się, że twoja mapa niewiele się zmieni. Jeśli chcesz renderować mapy podrzędne w miejscu (na przykład wnętrze budynku), po prostu renderuj to również na płótnie, a następnie narysuj na wierzchu. Oto przykład, w jaki sposób możesz użyć tego do renderowania mapy:
Spowoduje to narysowanie niewielkiej części mapy wokół
mapCentreX, mapCentreY
. Umożliwi to płynne przewijanie całej mapy, a także ruch pod kafelkami - możesz zapisać pozycję gracza jako 1/32 części kafelka, dzięki czemu możesz uzyskać płynny ruch po mapie (oczywiście, jeśli chcesz przesuń płytkę po płytce jako stylistyczny wybór, możesz po prostu zwiększyć o 32 części.Jeśli chcesz, możesz jeszcze podzielić mapę lub jeśli twój świat jest ogromny i nie zmieściłby się w pamięci w systemach docelowych (pamiętaj, że nawet dla 1 bajtu na piksel mapa 512 x 352 ma 176 KB), ale wcześniej -renderując mapę w ten sposób, zauważysz duży wzrost wydajności w większości przeglądarek.
Zapewnia to elastyczność ponownego wykorzystywania kafelków na całym świecie bez konieczności edytowania jednego dużego obrazu, ale umożliwia także szybkie i łatwe uruchamianie tego kafelka i rozważenie tylko jednej „mapy” (lub tylu „map”, ile chcesz) .
źródło
We wszystkich implementacjach, które widziałem, mapy kafelków są często generowane jako jeden duży obraz, a następnie „dzielone” na mniejsze części.
https://gamedev.stackexchange.com/a/25035/10055
Te fragmenty mają zwykle moc 2, więc optymalnie mieszczą się w pamięci.
źródło
Łatwym sposobem na śledzenie identyfikatorów byłoby po prostu użycie innej tablicy 2d z nimi: utrzymując x, y na tej „meta-tablicy”, możesz łatwo wyszukać inne identyfikatory.
Biorąc to pod uwagę, oto podejście Google do gry 2D na kafelkach na płótnie (no cóż, właściwie multiplayer HTML5, ale silnik kafelkowy jest również objęty) z ich ostatniego we / wy 2012: http://www.youtube.com/watch?v=Prkyd5n0P7k It dostępny jest również kod źródłowy.
źródło
Twój pomysł, aby najpierw załadować całą mapę do tablicy, jest skutecznym sposobem, ale łatwo będzie uzyskać JavaScript Injection'd, każdy będzie mógł poznać mapę i tam jest przygoda.
Pracuję również nad grą RPG w Internecie, sposób w jaki ładuję moją mapę to Ajax ze strony PHP, która ładuje mapę z bazy danych, gdzie jest napisane X, Y, pozycja Z i vlaue płytki, narysuj ją, a na koniec gracz ruszy się.
Nadal pracuję nad tym, aby uczynić go bardziej wydajnym, ale mapa ładuje się wystarczająco szybko, aby utrzymać go w ten sposób.
źródło
Nie , tablica jest najbardziej efektywnym sposobem przechowywania mapy kafelków .
Może istnieć kilka struktur, które wymagają nieco mniej pamięci, ale tylko kosztem znacznie wyższych kosztów ładowania i dostępu do danych.
Należy jednak wziąć pod uwagę, kiedy zamierzasz załadować następną mapę. Biorąc pod uwagę, że mapy nie są szczególnie duże, rzeczą, która faktycznie zajmie najdłużej, jest czekanie, aż serwer dostarczy mapę. Rzeczywiste ładowanie zajmie tylko kilka milisekund. Ale to oczekiwanie może odbywać się asynchronicznie, nie musi blokować przepływu gry. Najlepszą rzeczą jest nie ładowanie danych w razie potrzeby, ale wcześniej. Gracz jest na jednej mapie, teraz załaduj wszystkie sąsiednie mapy. Gracz porusza się na następnej mapie, ponownie ładuje wszystkie sąsiednie mapy itp. Itp. Gracz nawet nie zauważy, że gra się ładuje w tle.
W ten sposób możesz również stworzyć iluzję świata bez granic, rysując także sąsiednie mapy, w takim przypadku musisz załadować nie tylko sąsiednie mapy, ale także te sąsiadujące z nimi.
źródło
Nie jestem pewien, dlaczego tak myślisz: jak widzisz, wcześniej czy później na wielu mapach będę miał do czynienia z wieloma identyfikatorami. A to może być trochę mylące i nerwowe.
LeftID zawsze będzie wynosił -1 od currentID, rightID zawsze będzie +1 od currentID.
UpID zawsze będzie wynosił - (całkowita szerokość mapy) bieżącego ID, a downID zawsze będzie + (całkowita szerokość mapy) bieżącego ID, z wyjątkiem zera, co oznacza, że trafiłeś na krawędź.
Pojedyncza mapa może zostać podzielona na wiele map i są zapisywane sekwencyjnie z mapIDXXXX, gdzie XXXX jest identyfikatorem. W ten sposób nie ma co się mylić. Byłem na twojej stronie i wydaje mi się, że mogę się mylić, że problem leży po stronie edytora map, który narzuca ograniczenie rozmiaru, które utrudnia twoją automatyzację dzielenia dużej mapy na wiele małych przy zapisywaniu, a to ograniczenie jest prawdopodobnie ograniczające rozwiązanie techniczne.
Napisałem edytor map Javascript, który przewija we wszystkich kierunkach, 1000 x 1000 (nie polecam tego rozmiaru) kafelków i nadal porusza się tak dobrze, jak mapa 50 x 50, czyli z 3 warstwami, w tym przewijaniem paralaksy. Zapisuje i odczytuje w formacie json. Wyślę ci kopię, jeśli powiesz, że nie przeszkadza ci wiadomość e-mail o wielkości około 2 meg. To ma być na githubie, ale nie mam przyzwoitego (legalnego) zestawu kafelków, dlatego nie zadałem sobie trudu, żeby go tam umieścić.
źródło