Co musisz wiedzieć, konwertując grę Flash na HTML5?
Opublikowany: 2022-03-10Wraz ze wzrostem użycia HTML5, wiele firm zaczyna przerabiać swoje najpopularniejsze tytuły, aby pozbyć się przestarzałego Flasha i dopasować swoje produkty do najnowszych standardów branżowych. Ta zmiana jest szczególnie widoczna w branży hazardowej/kasynowej i rozrywkowej i zachodzi już od kilku lat, więc przyzwoity wybór tytułów został już przekonwertowany.
Niestety podczas przeglądania Internetu dość często można natknąć się na przykłady pozornie pospiesznej pracy, która przekłada się na lepszą jakość finalnego produktu. Dlatego dobrym pomysłem jest, aby twórcy gier poświęcili trochę czasu na zapoznanie się z tematem konwersji Flash na HTML5 i nauczenie się, jakich błędów unikać przed przystąpieniem do pracy.
Wśród powodów wyboru JavaScript zamiast Flasha, poza oczywistymi problemami technicznymi, jest również fakt, że zmiana projektu gry z SWF na JavaScript może zapewnić lepsze wrażenia użytkownika, co z kolei nada jej nowoczesny wygląd. Ale jak to zrobić? Czy potrzebujesz dedykowanego konwertera gier JavaScript, aby pozbyć się tej przestarzałej technologii? Cóż, konwersja Flash do HTML5 może być bułka z masłem — oto jak się tym zająć.
Zalecana literatura : Zasady projektowania gier HTML5
Jak poprawić wrażenia z gry HTML5?
Konwersja gry na inną platformę to doskonała okazja, aby ją ulepszyć, naprawić problemy i zwiększyć liczbę odbiorców. Poniżej kilka rzeczy, które można łatwo zrobić i które warto rozważyć:
Obsługa urządzeń mobilnych
Konwersja z Flasha na JavaScript umożliwia dotarcie do szerszego grona odbiorców (użytkowników urządzeń mobilnych); W grze zazwyczaj trzeba również zaimplementować obsługę elementów sterujących na ekranie dotykowym. Na szczęście zarówno urządzenia z Androidem, jak i iOS obsługują teraz również WebGL, więc renderowanie 30 lub 60 FPS zwykle można łatwo osiągnąć. W wielu przypadkach 60 FPS nie przysporzy żadnych problemów, co z czasem ulegnie poprawie, ponieważ urządzenia mobilne będą coraz wydajniejsze.- Poprawa wydajności
Jeśli chodzi o porównywanie ActionScript i JavaScript, ten drugi jest szybszy niż pierwszy. Poza tym konwersja gry jest dobrą okazją do ponownego przyjrzenia się algorytmom używanym w kodzie gry. Dzięki tworzeniu gier JavaScript możesz je zoptymalizować lub całkowicie usunąć nieużywany kod pozostawiony przez oryginalnych programistów. - Naprawianie błędów i ulepszanie rozgrywki
Posiadanie nowych programistów, którzy zajmą się kodem źródłowym gry, może pomóc naprawić znane błędy lub odkryć nowe i bardzo rzadkie. Dzięki temu granie w grę będzie mniej irytujące dla graczy, co sprawi, że spędzą więcej czasu na Twojej stronie i zachęcą do wypróbowania innych gier. - Dodawanie analityki internetowej
Oprócz śledzenia ruchu, analityka internetowa może być również wykorzystywana do gromadzenia wiedzy o tym, jak gracze zachowują się w grze i gdzie utknęli podczas rozgrywki. - Dodawanie lokalizacji
Zwiększyłoby to widownię i jest ważne dla dzieci z innych krajów grających w twoją grę. A może Twoja gra nie jest w języku angielskim i chcesz obsługiwać ten język?
Dlaczego pomijanie HTML i CSS dla interfejsu użytkownika w grze poprawi wydajność gry?
Jeśli chodzi o tworzenie gier JavaScript, może być kuszące wykorzystanie HTML i CSS dla przycisków w grze, widżetów i innych elementów GUI. Radzę tutaj uważać. Jest to sprzeczne z intuicją, ale w rzeczywistości wykorzystanie elementów DOM jest mniej wydajne w złożonych grach, a to nabiera większego znaczenia na urządzeniach mobilnych. Jeśli chcesz osiągnąć stałe 60 FPS na wszystkich platformach, może być konieczne zrezygnowanie z HTML i CSS.
Nieinteraktywne elementy GUI, takie jak paski zdrowia, paski amunicji lub liczniki wyników, można łatwo zaimplementować w Phaser, używając zwykłych obrazów (klasa Phaser.Image
), wykorzystując właściwość .crop
do przycinania i klasę Phaser.Text
do uproszczenia etykiety tekstowe.
Takie interaktywne elementy, jak przyciski i pola wyboru, można zaimplementować za pomocą wbudowanej klasy Phaser.Button
. Inne, bardziej złożone elementy mogą składać się z różnych prostych typów, takich jak grupy, obrazy, przyciski i etykiety tekstowe.
Uwaga: Za każdym razem, gdy tworzysz wystąpienie obiektu Phaser.Text lub PIXI.Text, tworzona jest nowa tekstura do renderowania tekstu. Ta dodatkowa tekstura przerywa grupowanie wierzchołków, więc uważaj, aby nie było ich zbyt wiele .
Jak upewnić się, że załadowano niestandardowe czcionki?
Jeśli chcesz renderować tekst za pomocą niestandardowej czcionki wektorowej (np. TTF lub OTF), musisz upewnić się, że czcionka została już załadowana przez przeglądarkę przed renderowaniem jakiegokolwiek tekstu. Phaser v2 nie zapewnia rozwiązania do tego celu, ale można użyć innej biblioteki: Web Font Loader.
Zakładając, że masz plik czcionki i zawierasz Web Font Loader na swojej stronie, poniżej znajduje się prosty przykład ładowania czcionki:
Utwórz prosty plik CSS, który zostanie załadowany przez Web Font Loader (nie musisz umieszczać go w swoim HTML):
@font-face { // This name you will use in JS font-family: 'Gunplay'; // URL to the font file, can be relative or absolute src: url('../fonts/gunplay.ttf') format('truetype'); font-weight: 400; }
Teraz zdefiniuj zmienną globalną o nazwie WebFontConfig
. Coś tak prostego jak to zwykle wystarczy:
var WebFontConfig = { 'classes': false, 'timeout': 0, 'active': function() { // The font has successfully loaded... }, 'custom': { 'families': ['Gunplay'], // URL to the previously mentioned CSS 'urls': ['styles/fonts.css'] } };
Na koniec pamiętaj, aby umieścić swój kod w 'aktywnym' wywołaniu zwrotnym pokazanym powyżej. I to wszystko!
Jak ułatwić użytkownikom zapisywanie gry?
Do trwałego przechowywania danych lokalnych w języku ActionScript należy użyć klasy SharedObject. W JavaScript prostym zamiennikiem jest localStorage API, który umożliwia przechowywanie ciągów do późniejszego pobrania, przetrwania przeładowań strony.
Zapisywanie danych jest bardzo proste:
var progress = 15; localStorage.setItem('myGame.progress', progress);
Zauważ, że w powyższym przykładzie zmienna progress
, która jest liczbą, zostanie przekonwertowana na ciąg.
Ładowanie też jest proste, ale pamiętaj, że pobierane wartości będą ciągami lub null
, jeśli nie istnieją.
var progress = parseInt(localStorage.getItem('myGame.progress')) || 0;
Tutaj upewniamy się, że zwracana wartość jest liczbą. Jeśli nie istnieje, do zmiennej progress
zostanie przypisane 0.
Możesz także przechowywać i pobierać bardziej złożone struktury, na przykład JSON:
var stats = {'goals': 13, 'wins': 7, 'losses': 3, 'draws': 1}; localStorage.setItem('myGame.stats', JSON.stringify(stats)); … var stats = JSON.parse(localStorage.getItem('myGame.stats')) || {};
W niektórych przypadkach obiekt localStorage nie będzie dostępny. Na przykład podczas korzystania z protokołu file://
lub gdy strona jest ładowana w oknie prywatnym. Możesz użyć instrukcji try and catch, aby upewnić się, że Twój kod będzie działał i używał wartości domyślnych, co pokazano w poniższym przykładzie:
try { var progress = localStorage.getItem('myGame.progress'); } catch (exception) { // localStorage not available, use default values }
Inną rzeczą do zapamiętania jest to, że przechowywane dane są zapisywane według domeny, a nie według adresu URL. Jeśli więc istnieje ryzyko, że wiele gier jest hostowanych na jednej domenie, lepiej użyć prefiksu (przestrzeni nazw) podczas zapisywania. W powyższym przykładzie 'myGame.'
to taki prefiks i zwykle chcesz go zastąpić nazwą gry.
Uwaga : jeśli gra jest osadzona w ramce iframe, localStorage nie będzie zachowywać się w systemie iOS. W takim przypadku musisz zamiast tego przechowywać dane w nadrzędnym iframe .
Jak wykorzystać zastępowanie domyślnego shadera fragmentów
Kiedy Phaser i PixiJS renderują twoje sprite'y, używają prostego wewnętrznego Fragment Shadera. Nie ma wielu funkcji, ponieważ jest dostosowany do prędkości. Możesz jednak zastąpić ten moduł cieniujący do swoich celów. Na przykład można go wykorzystać do sprawdzenia przerysowania lub obsługi większej liczby funkcji renderowania.
Poniżej znajduje się przykład, jak dostarczyć własny domyślny Fragment Shader do Phasera v2:
function preload() { this.load.shader('filename.frag', 'shaders/filename.frag'); } function create() { var renderer = this.renderer; var batch = renderer.spriteBatch; batch.defaultShader = new PIXI.AbstractFilter(this.cache.getShader('filename.frag')); batch.setContext(renderer.gl); }
Uwaga: Należy pamiętać, że domyślny shader jest używany dla WSZYSTKICH duszków, a także podczas renderowania do tekstury. Pamiętaj też, że używanie złożonych shaderów dla wszystkich duszków w grze znacznie zmniejszy wydajność renderowania .

Jak zmienić metodę barwienia za pomocą domyślnego cieniowania?
Niestandardowy domyślny shader może zostać użyty do zastąpienia domyślnej metody barwienia w Phaserze i PixiJS.
Tinting w Phaserze i PixiJS polega na pomnożeniu pikseli tekstury przez dany kolor. Mnożenie zawsze przyciemnia kolory, co oczywiście nie stanowi problemu; różni się po prostu od barwienia Flash. W jednej z naszych gier musieliśmy zaimplementować podbarwianie podobne do Flasha i zdecydowaliśmy, że można użyć niestandardowego domyślnego shadera. Poniżej znajduje się przykład takiego Fragment Shadera:
// Specific tint variant, similar to the Flash tinting that adds // to the color and does not multiply. A negative of a color // must be supplied for this shader to work properly, ie set // sprite.tint to 0 to turn whole sprite to white. precision lowp float; varying vec2 vTextureCoord; varying vec4 vColor; uniform sampler2D uSampler; void main(void) { vec4 f = texture2D(uSampler, vTextureCoord); float a = clamp(vColor.a, 0.00001, 1.0); gl_FragColor.rgb = f.rgb * vColor.a + clamp(1.0 - vColor.rgb/a, 0.0, 1.0) * vColor.a * fa; gl_FragColor.a = fa * vColor.a; }
Ten moduł cieniujący rozjaśnia piksele, dodając kolor bazowy do odcienia. Aby to zadziałało, musisz podać negatyw koloru, który chcesz. Dlatego, aby uzyskać kolor biały, należy ustawić:
sprite.tint = 0x000000; // This colors the sprite to white Sprite.tint = 0x00ffff; // This gives red
Wynik w naszej grze wygląda tak (zauważ, jak czołgi migają na biało po trafieniu):

Jak sprawdzić overdraw w celu wykrycia problemów z szybkością wypełniania
Zastąpienie domyślnego modułu cieniującego może również pomóc w debugowaniu. Poniżej wyjaśniłem, jak można wykryć overdraw za pomocą takiego shadera.
Przerysowywanie ma miejsce, gdy wiele lub wszystkie piksele na ekranie są renderowane wielokrotnie. Na przykład wiele obiektów zajmujących to samo miejsce i renderowanych jeden nad drugim. Ile pikseli może renderować GPU na sekundę, jest określane jako szybkość wypełniania. Nowoczesne procesory graficzne do komputerów stacjonarnych mają nadmierną szybkość wypełniania do zwykłych celów 2D, ale te mobilne są znacznie wolniejsze.
Istnieje prosta metoda sprawdzenia, ile razy każdy piksel na ekranie został zapisany, zastępując domyślny globalny Fragment Shader w PixiJS i Phaserze następującym:
void main(void) { gl_FragColor.rgb += 1.0 / 7.0; }
Ten moduł cieniujący rozjaśnia piksele, które są przetwarzane. Liczba 7,0 wskazuje, ile zapisów jest potrzebnych, aby piksel stał się biały; możesz dostroić ten numer do własnych upodobań. Innymi słowy, jaśniejsze piksele na ekranie zostały zapisane kilka razy, a białe piksele zostały zapisane co najmniej 7 razy.
Ten moduł cieniujący pomaga również znaleźć zarówno „niewidoczne” obiekty, które z jakiegoś powodu są nadal renderowane, jak i duszki, które mają nadmierne przezroczyste obszary wokół, które należy usunąć (GPU nadal musi przetwarzać przezroczyste piksele w twoich teksturach).

Obrazek po lewej stronie pokazuje, jak gracz widzi grę, a obrazek po prawej pokazuje efekt zastosowania cieniowania overdraw do tej samej sceny.
Dlaczego silniki fizyczne są twoimi przyjaciółmi
Silnik fizyki to oprogramowanie pośredniczące, które odpowiada za symulację ciał fizycznych (zwykle dynamiki ciał sztywnych) i ich kolizji. Silniki fizyki symulują przestrzenie 2D lub 3D, ale nie jedno i drugie. Typowy silnik fizyczny zapewni:
- ruch obiektu poprzez ustawienie prędkości, przyspieszeń, stawów i silników;
- wykrywanie kolizji między różnymi typami kształtów;
- obliczanie reakcji na kolizję, czyli jak dwa obiekty powinny reagować, gdy się zderzają.
W Merixstudio jesteśmy wielkimi fanami silnika fizyki Box2D i używaliśmy go przy kilku okazjach. Istnieje wtyczka Phaser, która dobrze się w tym celu sprawdza. Box2D jest również używany w silniku gry Unity i GameMaker Studio 2.
Chociaż silnik fizyczny przyspieszy Twój rozwój, musisz zapłacić pewną cenę: zmniejszoną wydajność w czasie wykonywania. Wykrywanie kolizji i obliczanie odpowiedzi to zadanie wymagające dużej mocy obliczeniowej procesora. Możesz być ograniczony do kilkudziesięciu dynamicznych obiektów w scenie na telefonach komórkowych lub napotkać obniżoną wydajność, a także zmniejszoną liczbę klatek na sekundę poniżej 60 FPS.

Lewa część obrazu to scena z gry, podczas gdy prawa strona pokazuje tę samą scenę z nakładką debugowania fizyki Phaser wyświetlaną na górze.
Jak wyeksportować dźwięki z pliku .fla
Jeśli masz efekty dźwiękowe gry Flash w pliku .fla , to eksportowanie ich z GUI nie jest możliwe (przynajmniej nie w Adobe Animate CC 2017) ze względu na brak opcji menu służącej do tego celu. Ale jest inne rozwiązanie — dedykowany skrypt, który właśnie to robi:
function normalizeFilename(name) { // Converts a camelCase name to snake_case name return name.replace(/([AZ])/g, '_$1').replace(/^_/, '').toLowerCase(); } function displayPath(path) { // Makes the file path more readable return unescape(path).replace('file:///', '').replace('|', ':'); } fl.outputPanel.clear(); if (fl.getDocumentDOM().library.getSelectedItems().length > 0) // Get only selected items var library = fl.getDocumentDOM().library.getSelectedItems(); else // Get all items var library = fl.getDocumentDOM().library.items; // Ask user for the export destination directory var root = fl.browseForFolderURL('Select a folder.'); var errors = 0; for (var i = 0; i < library.length; i++) { var item = library[i]; if (item.itemType !== 'sound') continue; var path = root + '/'; if (item.originalCompressionType === 'RAW') path += normalizeFilename(item.name.split('.')[0]) + '.wav'; else path += normalizeFilename(item.name); var success = item.exportToFile(path); if (!success) errors += 1; fl.trace(displayPath(path) + ': ' + (success ? 'OK' : 'Error')); } fl.trace(errors + ' error(s)');
Jak używać skryptu do eksportowania plików dźwiękowych:
- Zapisz powyższy kod jako plik .jsfl na swoim komputerze;
- Otwórz plik .fla w programie Adobe Animate;
- Wybierz „Polecenia” → „Uruchom polecenie” z górnego menu i wybierz skrypt w otwartym oknie dialogowym;
- Teraz pojawia się kolejny plik dialogowy do wyboru katalogu docelowego eksportu.
I zrobione! Powinieneś teraz mieć pliki WAV w określonym katalogu. Pozostało tylko przekonwertować je na na przykład MP3, OGG lub AAC.
Jak korzystać z plików MP3 w konwersji Flash na HTML5
Stary dobry format MP3 powrócił, ponieważ niektóre patenty wygasły i teraz każda przeglądarka może dekodować i odtwarzać pliki MP3. To sprawia, że programowanie jest nieco łatwiejsze, ponieważ w końcu nie ma potrzeby przygotowywania dwóch oddzielnych formatów audio. Wcześniej potrzebne były na przykład pliki OGG i AAC, teraz wystarczy MP3.
Niemniej jednak są dwie ważne rzeczy, o których należy pamiętać w przypadku MP3:
- MP3 trzeba zdekodować po załadowaniu, co może być czasochłonne, zwłaszcza na urządzeniach mobilnych. Jeśli zobaczysz pauzę po załadowaniu wszystkich zasobów, prawdopodobnie oznacza to, że MP3 jest dekodowane;
- bezproblemowe odtwarzanie zapętlonych plików MP3 jest trochę problematyczne. Rozwiązaniem jest użycie mp3loop, o którym można przeczytać w artykule zamieszczonym przez Compu Phase.
Dlaczego więc przekonwertować Flash na JavaScript?
Jak widać, konwersja Flash do JavaScript nie jest niemożliwa, jeśli wiesz, co robić. Mając wiedzę i umiejętności, możesz przestać zmagać się z Flashem i cieszyć się płynnymi, zabawnymi grami stworzonymi w JavaScript. Nie próbuj naprawiać Flasha — pozbądź się go, zanim wszyscy będą do tego zmuszeni!
Chcesz dowiedzieć się więcej?
W tym artykule skupiłem się głównie na Phaserze v2. Jednak nowsza wersja Phasera jest już dostępna i gorąco zachęcam do jej wypróbowania, ponieważ wprowadziła mnóstwo świeżych, fajnych funkcji, takich jak wiele kamer, sceny, mapy kafelków lub silnik fizyczny Matter.js.
Jeśli jesteś wystarczająco odważny i chcesz tworzyć naprawdę niezwykłe rzeczy w przeglądarkach, WebGL jest właściwą rzeczą, której możesz nauczyć się od podstaw. Jest to niższy poziom abstrakcji niż różne frameworki lub narzędzia do tworzenia gier, ale pozwala osiągnąć większą wydajność i jakość, nawet jeśli pracujesz nad grami 2D lub demami. Wśród wielu witryn, które mogą okazać się przydatne podczas nauki podstaw WebGL, znajdują się WebGL Fundamentals (wykorzystuje interaktywne demonstracje). Ponadto, aby dowiedzieć się więcej o wskaźnikach wykorzystania funkcji WebGL, sprawdź Statystyki WebGL.
Zawsze pamiętaj, że nie ma czegoś takiego jak za dużo wiedzy — zwłaszcza jeśli chodzi o tworzenie gier!