Notatki programisty: integracja PhysicsFS z SFML
03.01.2017 11:50
W poniższym wpisie chciałbym przedstawić prosty sposób na wykorzystanie plików z rozszerzeniem *.zip do przechowywania zasobów, w aplikacji używającej SFML. Motywacją do wpisu był fakt że przeszukując internet, nie znalazłem gotowego działającego kawałka kodu który zapewniłby mi taką funkcjonalność. Przechodząc do konkretów, zapraszam do lektury.
PhysicsFS
Do odczytu plików z archiwum *.zip będziemy używać biblioteki PhysicsFS. Jest to otwarto-źródłowa bibliotek wydana na licencji zlib dzięki czemu możemy być pewni że nikt nie będzie wołać o otwarcie naszego kodu. Mimo iż ostatnia aktualizacja miała miejsce w 2012 roku, format *.zip jest na tyle powszechny i uniwersalny że nie jest to jakaś duża niedogodność. Co prawda można ubolewać nad brakiem rozwoju projektu gdyż, prawdopodobnie, nie doczekamy się w nim funkcjonalności zabezpieczenia archiwum, hasłem, ale na potrzeby prostej aplikacji sprawdzi się w sam raz.
Biblioteka jest rozpowszechniana w postaci kodu źródłowego, co oznacza że sami musimy ją zbudować. W poniżej przedstawionym przykładzie, do zbudowania kodu używam QtCreatora z MinGW ze skonfigurowanym Cmakem w IDE. Uważam to za bardzo wygodne połączenie gdyż całość odbywa się w jednym oknie. Instrukcję jak skonfigurować Cmake w QtCreatorze znajdziemy tutaj.
Mając przystosowane środowisko, otwieramy plik CmakeLists.txt, projekt zostaje otworzony. Wybieramy typ budowy na release gdyż interesuje nas wersja bez znaczników debugowania. Przechodząc do do zakładki Projekty, mamy możliwość spersonalizowania budowanej biblioteki. Możemy wybrać jakie formaty mają być wspierane itp. PhysicsFS poza *.zip’em, oferuje wsparcie dla kilku innych rozszerzeń, więcej informacji znajduje się na stronie projektu. Wracając do tematu głównego: zatwierdzamy konfigurację i budujemy projekt.
Jako że wybrałem w konfiguracji opcje OFF w polu PHYSFS_BUILD_STATIC, interesującym mnie wynikiem kompilacji będą pliki: libphysfs.dll oraz libphysfs.dll.a. Następnie dobrze jest uporządkować katalog bibliotek. Wyżej wspomniane pliki przerzucamy do katalogu o nazwie lib, nagłówki ze źródeł PhysicFS przenosimy do nowego katalogu o nazwie: include, całość zamykamy w folderze o nazwie physicfs.
Integracja z SFML.
Pozwolę sobie nie opisywać procesu budowania SFML’a pod QtCreatorem. Jest to dobrze wytłumaczone w podanym przeze mnie linku dotyczącym konfiguracji Cmake’a lub też jak kto woli: analogiczny do budowania PhysicsFS. Na potrzeby przykładowej aplikacji utworzymy archiwum o następującej strukturze.
[code=] data | | -‑- > textures (katalog zawierający pliki cpp.png oraz uszatek.png) | -‑- > audio (katalog zawierajacy neverwinter_theme.ogg ) [/code]
W archiwum będziemy przechowywać najpopularniejsze zasoby z których korzystają proste gry indie czyli tekstury i audio. Tworzymy projekt w QtCreatorze, dołączamy zależności i możemy przystąpić do tego co tygryski lubią najbardziej. Czyli kodu. ;) Poniżej przedstawione zostało jak będą wyglądać nasze funkcje ładujące pliki z archiwum.
[code=c++] /* Funkcja ladowania tekstur z objasnieniem. */ sf::Texture* getTextureFromZip(std::string name) { sf::Texture* texturePtr = nullptr;
/* Test czy plik istnieje w obszarze poszukiwan.*/ if(PHYSFS_exists(name.c_str())) { std::cout << name + " - exists. \n";
/* Otwieramy plik i przypisujemy do niego * wskaznik. Nastepnie odczytujemy dane do * buffora oraz ilosc danych (jak duzo ich jest) */ PHYSFS_File* fileFromZip = PHYSFS_openRead(name.c_str());
const int fileLenght = PHYSFS_fileLength(fileFromZip); char* buffer = new char[fileLenght]; int readLength = PHYSFS_read (fileFromZip, buffer, 1, fileLenght);
/* Tworzymy obiekt SFML w pamieci. */ texturePtr = new sf::Texture(); texturePtr->loadFromMemory(buffer, readLength);
/* Nie trzeba chyba tlumaczyc, prawda? ;) */ PHYSFS_close(fileFromZip);
} else { std::cout << name + " - doesn't exists. \n"; }
return texturePtr; }
/* Analogiczna funkcja dla plikow audio. */ sf::SoundBuffer* getSoundBufferFromZip(std::string name) { sf::SoundBuffer* soundBufferPtr = nullptr;
if(PHYSFS_exists(name.c_str())) { std::cout << name + " - exists. \n";
PHYSFS_File* fileFromZip = PHYSFS_openRead(name.c_str());
const int fileLenght = PHYSFS_fileLength(fileFromZip); char* buffer = new char[fileLenght]; int readLength = PHYSFS_read (fileFromZip, buffer, 1, fileLenght);
soundBufferPtr = new sf::SoundBuffer(); soundBufferPtr->loadFromMemory(buffer, readLength);
PHYSFS_close(fileFromZip);
} else { std::cout << name + " - doesn't exists. \n"; }
return soundBufferPtr; } [/code]
Myślę że komentarz zawarty w kodzie jest wystarczająco wyczerpujący. Poniżej kod naszej funkcji głównej:
[code=c++] int main(int argc, char *argv[]) { /* Zainicjuj PHYSFS i dodaj plik *.zip * do "obszaru poszukiwań". */ PHYSFS_init(nullptr); PHYSFS_addToSearchPath("data.zip", 1);
/* Funkcje zwracaja wskazniki wiec warto * sie zabezpieczyc przed ewentualnym * zapomnieniem o dealokacji pamieci. Stad * std::unique_ptr. Zakladamy ze funkcje * nie zwroci nam nullptr stad brak testu. * Co moze pojsc zle? ;) */ std::unique_ptr<sf::Texture> textureCpp( getTextureFromZip("texture/cpp.png"));
std::unique_ptr<sf::Texture> textureUszatek( getTextureFromZip("texture/uszatek.png"));
std::unique_ptr<sf::SoundBuffer> soundBufferNeverwinter( getSoundBufferFromZip("audio/neverwinter_theme.ogg"));
/* Po zakonczeniu pracy z PHYSFS * nalezy go zdeinicjalizowac. */ PHYSFS_deinit();
/* Standardowy kod apki SFML. */ sf::RenderWindow window(sf::VideoMode(600, 400, 32), "AppZip", sf::Style::Default);
sf::RectangleShape shapeCpp; shapeCpp.setSize(sf::Vector2f(150,150)); shapeCpp.setPosition(sf::Vector2f(50, 50)); shapeCpp.setTexture(textureCpp.get());
sf::RectangleShape shapeUszatek; shapeUszatek.setSize(sf::Vector2f(150,150)); shapeUszatek.setPosition(sf::Vector2f(300, 50)); shapeUszatek.setTexture(textureUszatek.get());
sf::Sound soundNeverwinterTheme; soundNeverwinterTheme.setBuffer(*soundBufferNeverwinter.get()); soundNeverwinterTheme.play();
while(window.isOpen()) { sf::Event myEvent; while(window.pollEvent(myEvent)){
if(myEvent.type == sf::Event::Closed){ window.close(); } }
window.clear(); window.draw(shapeCpp); window.draw(shapeUszatek); window.display(); }
return 0; } [/code]
Tu tak samo jak w przypadku kodu funkcji, myślę że komentarze wyczerpują temat. Po zbudowaniu aplikacji wszystko powinno wyglądać a nawet śpiewać. ;) Niestety na blogu nie zaprezentuje działającego dźwięku przez co zachęcam do własnoręcznego sprawdzenia kodu.
Podsumowanie.
W ten oto sposób dobrnęliśmy do końca. Na początku wpisu wspomniałem że biblioteka nie jest już rozwijana i prawdopodobnie nie zostanie w niej zaimplementowany mechanizm zabezpieczania archiwum hasłem. Na pocieszenie, wspomnę o tym że jeden z najlepszych hack’n’slashy ostatnich lat, Torchlight, przechowywał swoje zasoby w niezabezpieczonym pliku *.zip o ile dobrze pamiętam. Jak widać, można w komercyjnym produkcie zawierać tak "trywialne" rozwiązania.
Co więcej mógłbym zaproponować to jeżeli ktoś chciałby korzystać z PhysicsFS w większej aplikacji to warto jest go „ucywilizować” i opakować klasą ze spójnym interfejsem. Korzystanie z biblioteki w stylu strukturalnym na dłuższą metę wydaje się niewygodne i mało praktyczne.
Dzięki za uwagę. ;)