Jakie są wyzwania i zalety pisania gier w funkcjonalnym języku?

81

Chociaż wiem, że języki funkcjonalne nie są najczęściej używane do pisania gier, wiąże się z nimi wiele korzyści, które wydają się interesujące w każdym kontekście programowania. Zwłaszcza łatwość równoległości, jak sądzę, może być bardzo przydatna, ponieważ skupia się na coraz większej liczbie procesorów.

Ponadto, dzięki F # jako nowemu członkowi rodziny .NET, można go używać bezpośrednio na przykład z XNA, co znacznie obniża próg, w przeciwieństwie do LISP, Haskell, Erlang itp.

Jeśli ktoś ma doświadczenie w pisaniu gier z kodem funkcjonalnym, co okazało się pozytywne i negatywne? Do czego był przeznaczony, a co nie?

Edycja: Trudno jest zdecydować, że jest na to jedna dobra odpowiedź, więc prawdopodobnie lepiej nadaje się jako post na wiki społeczności.

McMuttons
źródło
3
Ponieważ jedna z moich ulubionych gier (Łotr) została napisana w LISP, to pytanie (i potencjał odpowiedzi) jest dla mnie bardzo interesujące.
Justin L.,
Nie wiedziałem, że Rogue został napisany w LISP, ale uważam to za interesujące. Klasyczna gra. :)
McMuttons
2
Nie jestem pewien, o czym mówisz. Łotr został napisany w C. roguebasin.roguelikedevelopment.org/index.php?title=Rogue
Mason Wheeler
2
Jestem pewien, że masz rację co do tego, że oryginalny Łotrzyk jest w C, ponieważ został napisany na Uniksie, ale na pewno jest kilka nieuczciwych klonów w Lisp: cl-user.net/asp/root-dir (nie bezpośredni link do gry , miał tak wiele znaków specjalnych, że wydaje się, że ma zerwane powiązanie).
Cyklop

Odpowiedzi:

57

Obecnie pracuję nad grą w Haskell. Ogólnie nie mogę mówić o programowaniu funkcjonalnym, ale konkretnie w przypadku Haskell:

Dobry

To niesamowite rzeczy, które sprawiają, że Haskell naprawdę się wyróżnia.

  • Czystość oznacza, że ​​rozumowanie twojego kodu jest znacznie łatwiejsze. Nie musisz się martwić o „bieżącą” wartość zmiennej lub o to, czy użycie danej funkcji spowoduje konflikt ze stanem globalnym. Oznacza to również, że równoległość i współbieżność są znacznie łatwiejsze w obsłudze (aw wielu przypadkach banalne). Te rzeczy mogą być darem niebios dla twórców gier.
  • Haskell sprawia, że ​​pisanie na bardzo wysokim poziomie jest bardzo łatwe przy użyciu abstrakcji własnego dzieła. Często łatwiej jest pisać bardzo ogólny kod niż specjalistyczny w Haskell, a zalety tego przejawiają się w krótszym czasie programowania i łatwiejszym zrozumieniu kodu. Jest to bardziej prawdziwe w przypadku Haskell niż w jakimkolwiek innym języku, który znam, że używanie interfejsu jest jak używanie zupełnie nowego języka (przy jednoczesnym zachowaniu korzyści z pozostałej części ekosystemu Haskell). To, co większość języków nazywa funkcjami językowymi, Haskell nazywa biblioteką i nie czuje się zmuszony. Jeśli kiedykolwiek zastanawiasz się nad swoją grą w psuedocode, prawdopodobnie możesz po prostu napisać prosty interfejs i uczynić go prawdziwym kodem, i zwykle warto to zrobić.
  • Może to kolidować z innymi odpowiedziami, ale Haskell lepiej radzi sobie z kodem rozkazującym niż jakikolwiek inny język, który znam. Ograniczenia czystości oznaczają, że Haskell rozdziela pojęcia „ewaluacja” (redukcja wyrażeń) i „wykonanie” (wykonywanie efektów ubocznych). Możesz kontrolować, kiedy i jak efekty uboczne są wykonywane z łatwością niespotykaną w tak zwanych „imperatywnych” językach. Pomimo tego, co powiedziałem wcześniej o czystości, niektóre wiązania C są nadal bardzo konieczne (OpenGL, patrzę na ciebie), więc ta szczegółowa kontrola nad kodem imperatywnym jest bardzo pożądana. Nawet najbardziej doświadczeni programiści C docenią to, gdy się do tego przyzwyczają.
  • GHC generuje dość szybkie pliki binarne. Nie są one tak szybkie, jak pliki binarne generowane ze zwykłych kompilatorów C, ale mieszczą się w rzędzie wielkości i są znacznie szybsze niż w przypadku interpretowanego języka. Nawet jeśli nie możesz napisać swojej gry w innych językach wysokiego poziomu, takich jak Python, ponieważ byłoby to zbyt wolne, istnieje duża szansa, że ​​możesz to zrobić w Haskell.
  • Mimo że w Haskell nie ma wielu bibliotek związanych z grami, jak zauważono poniżej w „Złych”, interfejs funkcji obcych Haskell jest najłatwiejszy, jakiego kiedykolwiek używałem. Możesz połączyć się z C, pisząc prawie wyłącznie w języku Haskell.

Źli

Są to rzeczy, które nie są dobre, ale można obejść bez zbyt wiele wysiłku.

  • Czas i wysiłek potrzebny do nauki języka Haskell może być dość wysoki, jeśli jesteś bardzo przyzwyczajony do pracy z niefunkcjonalnymi językami. Sam język nie jest bardzo trudny, ale jeśli weźmiesz pod uwagę wspólne rozszerzenia językowe i ogromną liczbę bibliotek (pamiętaj, że wiele rzeczy, które możesz rozważyć w innych językach to biblioteki w Haskell), wygląda to znacznie bardziej złowieszczo. Moim zdaniem warto, ale inni mogą się nie zgodzić.
  • Niektórzy często narzekają na lenistwo. Jeśli wiesz, co robisz, nie spowodujesz wycieków kosmosu, które są trudne do wyśledzenia (nie stworzyłem przecieku kosmicznego od kilku lat), ale początkujący mają z tym dużo więcej problemów. Byłoby głupotą, gdybym zastrzelił tych ludzi i twierdził, że to nie jest problem, ale zapewniam, że jest to problem, który można łatwo rozwiązać za pomocą doświadczenia.
  • W Haskell nie ma wielu wspaniałych bibliotek do tworzenia gier, a niewielu Haskellerów pisze w nim gry, więc trudno jest znaleźć zasoby i pomoc w tej sprawie.

Brzydki

Są to rzeczy, których pokonanie wymagałoby dużego wysiłku.

  • Jeśli piszesz grę intensywnie korzystającą z zasobów, śmieciarz GHC może cię mocno ugryźć. Jest to generacyjny GC z zatrzymaniem świata, więc co jakiś czas możesz upuścić kilka klatek. W przypadku niektórych gier jest to w porządku, ale w przypadku innych jest to poważny problem. Ja i niektórzy inni twórcy gier korzystający z Haskell chcieliby zobaczyć GC w czasie rzeczywistym zaimplementowanym dla GHC, ale o ile mi wiadomo, nie poczyniono jeszcze żadnych wysiłków. Obecnie nie martwię się o obejście tego (i nie widziałem jeszcze żadnych opuszczonych klatek), ale przynajmniej jeden inny zespół uciekł się do umieszczenia swojej głównej pętli renderowania w C.
Jake McArthur
źródło
2
Co sądzisz o FRP?
Wei Hu
4
@Wei Hu: Jestem wielkim fanem idei FRP i sam dość intensywnie ją badałem, w tym tworząc kilka różnych semantyek i pisząc kilka implementacji. Najlepsze implementacje wciąż mają poważne błędy semantyczne i / lub implementacyjne. Powiedziałbym, że nadają się do tworzenia gier, ale nie mają wielu zalet (jeszcze) w porównaniu z tradycyjnymi podejściami.
Jake McArthur,
1
Anonimowy użytkownik próbował edytować tę odpowiedź, wycierając „brzydką” część. „Śmieciarka w Haskell jest teraz równoczesna, równoległa i pokoleniowa od 2011 r. ( Ghc.haskell.org/trac/ghc/blog/new-gc-preview )”
Jari Komppa
1
Niestety, tak naprawdę nie jest to prawdą. Jednoczesna GC okazała się zbyt skomplikowana, aby uzasadnić małą ogólną poprawę, i nie została połączona.
Jake McArthur
1
@ Hi-Angel Istnieje stary backend GHC, który generował C, ale nie różni się niczym od generatora kodu natywnego lub backendu LLVM. Nadal wymaga środowiska wykonawczego GHC. Ponadto nie ma nic specjalnego w backendzie C, który ułatwiłby unikanie śmieci. Gdyby wyeliminowanie GC było tak łatwe i tanie, inne backendy prawdopodobnie by to zrobiły.
Jake McArthur,
19

Ostatni rok spędziłem na opracowywaniu komercyjnego silnika gry w Haskell, a dla nas doświadczenie było zdecydowanie pozytywne. Nasz świat gier jest złożony, a Haskell ułatwił modelowanie procesu konwersji z formatu edytora do formatu silnika gry. Nie chciałbym myśleć, jak wyglądałby ten kod w języku imperatywnym.

Czasami pojawiały się przecieki kosmiczne i chociaż spowodowały trochę problemów, w ogólnym schemacie było to niewielkie (na przykład w porównaniu ze znalezieniem impasu w projektach Java o podobnej wielkości), a po ich naprawieniu , pozostały nieruchome.

Używamy FRP podobnego do Yampy iz pewnością wiąże się z tym krzywa uczenia się, ale kiedy to się skończy, doświadczenie jest bardzo pozytywne. Biblioteki nie stanowiły dla nas problemu - wszystko, czego potrzebowaliśmy, było dostępne. Opóźnienia związane z odśmiecaniem pamięci były szczególnym problemem, ponieważ dotyczy to wbudowanej platformy. Użyliśmy trochę C ++ do zarządzania animacją. Problemem była także wydajność, ponieważ jest to platforma wbudowana (= wolny procesor). Zrobiliśmy trochę C i przyglądamy się również nowym technologiom Haskell, takim jak przyspieszenie. Animator C ++ był wczesną decyzją projektową, a miejsca, w których kod jest zbyt wolny, to tylko bardzo małe obszary. Na dłuższą metę chcemy przetłumaczyć całe nasze C na Haskell.

Haskell sprawił, że trudna praca stała się łatwa, a wszystkie trudności, o których właśnie wspomniałem, były niewielkie w porównaniu z dużą ilością złożonego kodu, który stworzyliśmy, który jest czysty, łatwy do utrzymania i prawie niezniszczalny. Równoległość będzie wkrótce problemem w rozwoju gier i jesteśmy wyjątkowo dobrze przygotowani, aby sobie z tym poradzić. Niektóre z tego, co powiedziałem, mogą nie mieć zastosowania do małych projektów, ponieważ jesteśmy w tym na dłuższą metę, więc koszty początkowe, takie jak krzywe uczenia się, wsparcie biblioteki itp., Są znacznie mniejszym problemem.

Stephen Blackheath
źródło
7
Zbliża się 5 lat od opublikowania tej odpowiedzi; czy byłbyś skłonny to zaktualizować? Jak działał silnik gry komercyjnej? Jakie elementy okazały się cenne? Czy potrafiłeś wykorzystać równoległość, tak jak chciałeś?
Levi Morrison
17

Dave wspomina o kilku znakomitych punktach, chociaż muszę zaznaczyć, że Haskell rozwiązał oba jego problemy. Bezpaństwowość można obejść za pomocą monady państwowej (EDYCJA: nie bardzo - szczegóły poniżej) , a sekwencjonowanie można obsłużyć za pomocą monady IO (EDYCJA: lub dowolna inna monada, jeśli o to chodzi ...) .

Wyzwania, które będziesz miał (i że próbowałem nauczyć się programowania gier i Haskell), są bardziej podobne. (Wszystkie są specyficzne dla Haskell, ponieważ tak naprawdę nie zagłębiłem się jeszcze w żadne inne języki FP).

  • Krzywa uczenia się FP: FP wymaga całkowitego odejścia od programowania iteracyjnego. Nauka myślenia w oparciu o mapy i fałdy zamiast pętli wymaga treningu umysłowego, jeśli nie jesteś do tego przyzwyczajony.
  • Krzywa uczenia się matematyki : Haskell jest zarówno błogosławiony, jak i przeklęty matematycznym wyrafinowaniem swoich projektantów, ponieważ po nauce podstaw FP utkniesz z monadami, morfizmami, strzałami i całą masą innych rozważań, które, choć nie są trudne same w sobie są bardzo abstrakcyjne.
  • Monady: te szczenięta nie są szczególnie trudne do opanowania, szczególnie jeśli zaakceptujesz oświadczenie Erica Raymonda, że są one „hackem do przekształcenia kompozycji funkcji w sposób wymuszający sekwencjonowanie wywołań funkcji”. Niestety (choć staje się to coraz lepsze, gdy Haskell zyskuje na popularności), wiele wyjaśnień monad albo celuje ponad głowę większości programistów („są monoidami w kategorii endofunkorów!”), Albo całkowicie nieprzydatną analogią (np. , Niepokojąco dokładny przykład Brenta Yorgeya: „ monady są jak burrito !” Myślisz, że żartuje? Że nikt nigdy nie wymyśliłby tak głupiej analogii w tutorialu? Pomyśl jeszcze raz .)
  • Ekosystem: jeśli udało ci się wyjść poza wszystkie inne nierówności na drodze, będziesz musiał stawić czoła wyzwaniom praktycznej codziennej pracy z wciąż eksperymentalnym językiem. Niech Bóg ci pomoże, kiedy tu dotrzesz. Tutaj zacząłem poważnie jonesing dla Scali, F # lub jakiegoś innego języka, w którym istnieje przynajmniej pewna gwarancja interoperacyjności z innymi bibliotekami. Dawno, dawno temu zmusiłem Haskell do połączenia i załadowania bibliotek OpenGL w systemie Windows. To sprawiło, że czułem się całkiem dobrze. Następnie powiązania OpenGL zostały ponownie wydane i zepsuły wszystko, co zrobiłem. Takie jest życie w krainie Haskell. Szczerze mówiąc, może to być ból. Chcesz otoczyć silnik Bullet? Jest w hakowaniu - jako porzucone oprogramowanieod listopada ubiegłego roku. Lubisz Ogre3d? Hackage ma powiązania tylko z podzbiorem biblioteki . Najważniejsze jest to, że jeśli trzymasz się języka na uboczu podczas tworzenia gier, poświęcisz dużo więcej czasu na pracę nad podstawową instalacją wodną, ​​niż gdybyś po prostu śledził stado i trzymał się C ++.

Drugą stroną tego jest to, że rzeczy szybko się poprawiają. A tak naprawdę wszystko zależy od tego, czego chcesz od tego doświadczenia. Chcesz zbudować grę i umieścić ją na swojej stronie, aby szukać sławy i fortuny? Trzymaj się C ++ lub Pythona. Ale jeśli chcesz nauczyć się czegoś nowego, co wymagać będzie innowacji w swoich procesach, wypróbuj funkcjonalny język. Po prostu miej dużo cierpliwości do siebie podczas nauki.

Antti Salonen ma interesujący blog opisujący jego romans z programowaniem gier Haskell. Warto przeczytać.

Edycja (rok później): Teraz, gdy bardziej przestudiowałem monadę państwową, zdaję sobie sprawę, że nie jest to szczególnie dobre rozwiązanie dla stanu, który ma pozostać poza określoną funkcją. Prawdziwe rozwiązania bezpaństwowości można znaleźć w Haskell w IOVar, ST, MVar (dla bezpieczeństwa wątków) lub poprzez coś takiego jak Yampa, który używa Arrows i FRP do zarządzania stanem wewnętrznym, który jest jednak ukryty przed deweloperem. (Ta lista jest uporządkowana według stopnia trudności, choć pierwsze trzy nie są szczególnie trudne, gdy zrozumiesz monady).

rtperson
źródło
1
Kilka dobrych myśli, nawet jeśli dość specyficznych dla Haskella, myślę, że są to myśli, które dotyczą większości języków marginalnych. Jak krótko wspomniałeś, myślę, że to tutaj języki takie jak F # mogą błyszczeć. Na przykład obsługa stanu / interfejsu użytkownika za pomocą C #, a następnie moduły logiczne w języku F # dla algorytmów takich jak potencjalnie wyszukiwanie ścieżki, AI itp.
McMuttons
5

Obiektywny Caml!

Używanie języków funkcjonalnych może być ogromną zaletą w większości rodzajów tworzenia oprogramowania, głównie dlatego, że znacznie skracają czas programowania. Widzę duży potencjał do napisania zaplecza serwera do gry lub warstw sztucznej inteligencji i logiki na kliencie w funkcjonalnym języku. I jak wszyscy wiedzą, LISP został wykorzystany do tworzenia skryptów NPC.

Gdybym miał spróbować napisać frontend gry w funkcjonalnym języku, zdecydowanie wybrałbym Objective Caml , który jest językiem hybrydowym. Jest potomkiem ML i pozwala łączyć style iteracyjne i funkcjonalne, ma system obiektywny z obiektami stanowymi. (Caml to język, w którym modelowany jest F #).

Wydaje się, że wiązania OpenGL działają bez zarzutu, a ludzie tworzą gry w OCaml od dłuższego czasu. Język można skompilować i jest on potencjalnie bardzo szybki (dawno już dawno wygrał z C w niektórych testach porównawczych, nie wiem, jak teraz wygląda sytuacja).

Istnieje również mnóstwo bibliotek. Wszystko, od informatyki po powiązania i wszelkiego rodzaju biblioteki.

Feliksyz
źródło
4

Naprawdę nie jest to odpowiedź na nic, ale wydaje mi się, że dyskusja na temat programowania gier w językach funkcjonalnych jest niepełna bez wzmianki o Functional Reactive Programming (FRP) - http://www.haskell.org/frp/ - i jego najczęstszej implementacji ( Myślę, że?), YAMPA - http://www.haskell.org/yampa/ .

użytkownik744
źródło
3

Jeśli chodzi o korzyści, sprawdź Clean Game Library (http://cleangl.sourceforge.net/) i sprawdź grę platformową. Po obejrzeniu składni (zachęcam do wypróbowania) zobaczysz czystą elegancję konstruktów i to, jak ściśle źródło mapuje koncepcje, które wyrażają. Jako dodatkowy bonus abstrakcja stanu i konieczność jawnego przekazywania stanu sprawia, że ​​kod jest bardziej przyjazny dla wielu rdzeni.

Z drugiej strony wymaga to znacznej zmiany paradygmatu. Wiele z tego, co robiłeś bez myślenia o tym nagle, już nie istnieje i będziesz zastanawiać się, jak to rozwiązać, po prostu dlatego, że myślisz w niewłaściwy sposób.

Kaj
źródło
2

Z mojego punktu widzenia największymi wyzwaniami będą:

  • Bezpaństwowość języków funkcjonalnych: w grach chodzi o to, że każdy byt ma stan i ten stan jest modyfikowany z jednej klatki do drugiej. Istnieją mechanizmy naśladowania tego zachowania w niektórych językach (na przykład funkcje ze schematem „!” W programie plt), ale wydaje się to trochę nienaturalne.
  • Kolejność wykonywania : W grach niektóre części kodu zależą od innych części kodu, które należy wykonać przed wykonaniem. Języki funkcjonalne generalnie nie dają żadnych gwarancji co do kolejności wykonywania argumentów funkcji. Istnieją sposoby naśladowania tego zachowania (na przykład „ (begin...” w schemacie plt).

Nie krępuj się rozszerzyć tę listę (i być może dodać kilka pozytywnych punktów ^^).

Dave O.
źródło
1
Kolejność wykonywania może się różnić w nieokreślonym języku, takim jak Haskell, ale nie powoduje problemów z częściami kodu zależnymi od innych części kodu. Możesz myśleć o tym jak o ocenie na żądanie. Obliczenia nie są wykonywane, dopóki wynik nie jest potrzebny. Myślę, że jedynym sposobem, w jaki może to spowodować twój smutek, jest wydajność. Na przykład może to utrudnić uzyskanie dobrego buforowania w niektórych przypadkach, ponieważ nie będziesz mieć kontroli nad kolejnością ewaluacji poza określaniem zależności między obliczeniami.
Jason Dagit,
1

Możesz napisać swój kod w C / C ++ i użyć języka wbudowanego, takiego jak Embedded Common Lisp, do pisania skryptów. Chociaż kompilowanie ich na innej platformie może być trudne. Możesz spojrzeć na Lisp In Small Pieces, aby nauczyć się pisać własny język skryptowy. To naprawdę nie jest takie trudne, jeśli masz czas.

WarWeasle
źródło
0

Napisałem proste gry w LISP i było fajnie, ale nie polecam tego. W programowaniu funkcjonalnym chodzi o efekt końcowy. Jest to więc bardzo przydatne do przetwarzania danych i podejmowania decyzji, ale stwierdziłem, że utrudniało to wykonanie prostego, czystego kodu, takiego jak podstawowa pętla kontrolna.

Nie nauczyłem się F #, więc może być o wiele łatwiej pracować.

Jeff
źródło
Moim początkowym pomysłem może być napisanie rusztowania i obsługi stanu za pomocą imperatywnego języka, a następnie zrobienie logiki gry i tym podobnych za pomocą bitów funkcjonalnych. W przypadku .NET i C # / F # byłoby to na przykład bardzo łatwe, ponieważ używają tych samych bibliotek i mogą ze sobą rozmawiać bez żadnej znanej mi kary.
McMuttons,
-1

pozytywem byłaby wydajność i przenośność, a negatywnym byłoby zarządzanie złożonością , dzisiejsza gra jest bardzo złożona i potrzebuje narzędzi lub języka programowania, który lepiej poradzi sobie ze złożonością, takich jak metodologia obiektowa lub programowanie ogólne, które trudno jest zaimplementować w programowaniu funkcjonalnym język.

uray
źródło
11
Czy ty żartujesz? FP to ogólne programowanie na sterydach. Możliwość uogólnienia zarówno typów danych, jak i funkcji daje FP poważne możliwości zarządzania złożonością.
rtperson
Zgadzam się, jedną z największych zalet FP jest jego zdolność do generalizowania kodu.
McMuttons,