Czyste funkcjonalne programowanie i stan gry


12

Czy istnieje wspólna technika obsługi stanu (ogólnie) w funkcjonalnym języku programowania? W każdym (funkcjonalnym) języku programowania istnieją rozwiązania do obsługi stanu globalnego, ale chcę tego uniknąć w miarę możliwości.

Wszystkie stany w czysto funkcjonalny sposób są parametrami funkcji. Muszę więc ustawić cały stan gry (gigantyczna mapa świata, graczy, pozycje, wyniki, zasoby, wrogów, ...) jako parametr wszystkich funkcji, które chcą manipulować światem na danym wejściu lub wyzwalaczu . Sama funkcja pobiera odpowiednie informacje z obiektu blob gamestate, zrób coś z tym, manipuluj gamestate i zwróć gamestate. Ale wygląda to na kiepskie rozwiązanie problemu. Gdybym włożył cały gamestate do wszystkich funkcji, nie ma dla mnie korzyści w przeciwieństwie do zmiennych globalnych lub imperatywnego podejścia.

Mógłbym wstawić tylko odpowiednie informacje do funkcji i zwrócić działania, które zostaną podjęte dla danych wejściowych. Jedna funkcja stosuje wszystkie działania do gamestate. Ale większość funkcji wymaga wielu „istotnych” informacji. move()potrzebuję pozycji obiektu, prędkości, mapy kolizji, pozycji wszystkich wrogów, obecnego stanu zdrowia ... Więc to podejście też nie działa.

Więc moje pytanie brzmi: jak poradzić sobie z ogromną ilością stanów w funkcjonalnym języku programowania - szczególnie w przypadku tworzenia gier?

EDYCJA: W Clojure istnieje kilka platform do budowania gier. Podejście polegające na częściowym rozwiązaniu tego problemu polega na tym, że wszystkie przedmioty w grze są tak zwane „byty” i wkładane do ogromnej torby. Gigant główną funkcją trzyma ekran i podmioty i wydarzenia uchwyt ( :on-key-down, :on-init...) dla tej jednostki i uruchomić główną pętlę wyświetlacza. Ale nie jest to czyste rozwiązanie, którego szukam.


Przez jakiś czas myślałem o takich rzeczach; dla mnie to nie wejście jest unikalnym problemem, ponieważ nadal musisz (z grubsza) te same elementy do funkcji w programowaniu niefunkcjonalnym. Nie, problem stanowi wynik (i kolejne powiązane aktualizacje). Niektóre parametry wejściowe należy połączyć; ponieważ move()prawdopodobnie powinieneś przechodzić przez „bieżący” obiekt (lub jego identyfikator), plus świat, przez który się porusza, i po prostu wyliczyć aktualną pozycję i prędkość ... wyjście jest wtedy całym światem fizyki, a przynajmniej lista zmienionych obiektów.
Clockwork-Muse

zaletą czystej funkcjonalności jest to, że prototypy funkcji pokazują wszystkie zależności, jakie ma twój program.
tp1,

3
IMO, języki funkcjonalne są słabo dostosowane do pisania gier. To jeden z wielu problemów, które musisz rozwiązać. Gry wymagają bardzo precyzyjnej kontroli wydajności i rzadko mają dobrą współbieżność ze względu na nieprzewidywalny sposób naturalnego występowania zdarzeń. (Czyste) języki funkcjonalne wyróżniają się tym, że można je w prosty sposób zrównoleglać i trudno je zoptymalizować. Gra jest trudna do napisania, i zalecam po prostu robić to w typowym języku, zanim podejmie się czegoś równie złożonego (programowanie funkcjonalne).
Casey Kuball,

Odpowiedzi:


7

Skutki uboczne i stan w funkcjonalnych językach programowania są szerszym problemem w informatyce. Jeśli jeszcze ich nie spotkałeś, może rzuć okiem na monady . Ostrzegam jednak: są dość zaawansowaną koncepcją i większość ludzi, których znam (w tym mnie), stara się je zrozumieć. Istnieje wiele samouczków online, różniących się podejściem i wymaganiami wiedzy. Osobiście najbardziej lubiłem Erica Lipperta.

Jestem programistą C # bez żadnego „programowania programistycznego”. O czym jest ta „monada”, o której wciąż słyszę i jaki jest jej pożytek?

Eric Lippert o Monadach

Niektóre rzeczy do rozważenia:

  • Czy nalegasz na używanie czysto funkcjonalnego języka? Jeśli jesteś biegły zarówno w programowaniu funkcjonalnym, jak i tworzeniu gier, być może mógłbyś to zrobić. (Mimo że chciałbym wiedzieć, czy korzyści są warte wysiłku.)
  • Czy nie byłoby lepiej zastosować podejście funkcjonalne tylko w razie potrzeby? Jeśli używasz języka zorientowanego obiektowo (lub, co bardziej prawdopodobne, na wiele paradygmatów), nic nie stoi na przeszkodzie, aby używać funkcjonalnego stylu do implementowania korzystnych z niego sekcji. (Może trochę jak MapReduce?)

Kilka ostatnich myśli:

  • Równoległość: Podczas gry należy używać go mocno, AFAIK większość to już dzieje się na GPU w każdym razie.
  • Bezpaństwowość: Mutacje stanu są integralną częścią gier. Próbowanie się ich pozbyć może niepotrzebnie skomplikować sprawy.
  • Może zainteresuje Cię, jak język funkcjonalny F # gra z obiektowym ekosystemem .NET, jeśli jesteś zainteresowany.

Podsumowując, myślę, że nawet jeśli mogłoby to być interesujące z naukowego punktu widzenia, wątpię, aby takie podejście było praktyczne i warte wysiłku.


Po co komentować temat, w którym nie masz doświadczenia? Opinia pochodząca od ludzi uwięzionych w jednym paradygmacie myślenia.
Anthony Raimondo

4

Napisałem kilka gier przy użyciu F # (multi-paradygmat, nieczysty, język funkcjonalny-pierwszy), z podejściami od OOP do FRP . To szerokie pytanie, ale dam z siebie wszystko.

Czy istnieje wspólna technika obsługi stanu (ogólnie) w funkcjonalnym języku programowania?

Mój preferowany sposób to mieć niezmienny typ reprezentujący całą grę State. Następnie mam zmienne odniesienie do prądu, Statektóry jest aktualizowany za każdym razem. Nie jest to ściśle czysta, ale ogranicza zmienność do jednego miejsca.

Gdybym włożył cały gamestate do wszystkich funkcji, nie ma dla mnie korzyści w przeciwieństwie do zmiennych globalnych lub imperatywnego podejścia.

Nie prawda. Ponieważ ten Statetyp jest niezmienny, nie możesz mieć żadnego starego komponentu, który mutowałby świat w źle zdefiniowany sposób. To rozwiązuje największy problem z GameObjectpodejściem (spopularyzowanym przez Unity): trudno jest kontrolować kolejność Updatepołączeń.

I w przeciwieństwie do globałów, jest łatwo testowany jednostkowo i równoległy,

Powinieneś także napisać funkcje pomocnicze, które otrzymują pod-właściwości stanu, aby rozwiązać problem.

Na przykład:

let updateSpaceShip ship = 
  {
    ship with 
      Position = ship.Position + ship.Velocity
  }

let update state = 
  { 
    state with 
      SpaceShips = state.SpaceShips |> map updateSpaceShip 
  }

Tutaj updatedziała na cały stan, ale updateSpaceShipdziała tylko na jednostkę SpaceShipw izolacji.

Mógłbym wstawić tylko odpowiednie informacje do funkcji i zwrócić działania, które zostaną podjęte dla danych wejściowych.

Moją sugestią byłoby stworzenie Inputtypu, który przechowuje stany klawiatury, myszy, pada itp. Następnie możesz napisać funkcję, która pobiera Statei Inputzwraca następną State:

let applyInput input state = 
  // Something

Aby dać Ci wyobrażenie o tym, jak to pasuje, ogólna gra może wyglądać mniej więcej tak:

let mutable state = initialState ()

// Game loop
while true do
  // Apply user input to the world
  let input = collectInput ()

  state <- applyInput input state

  // Game logic
  let dt = computeDeltaTime ()

  state <- update dt state

  // Draw
  render state

Więc moje pytanie brzmi: jak poradzić sobie z ogromną ilością stanów w funkcjonalnym języku programowania - szczególnie w przypadku tworzenia gier?

W przypadku prostych gier możesz zastosować powyższe podejście (funkcje pomocnicze). Aby uzyskać coś bardziej skomplikowanego, możesz wypróbować „ soczewki ”.

W przeciwieństwie do niektórych tutaj komentarzy, zdecydowanie zalecałbym pisanie gier w (nieczystym) funkcjonalnym języku programowania, a przynajmniej spróbowanie! Przekonałem się, że dzięki temu iteracja jest szybsza, bazy kodu są mniejsze, a błędy mniej powszechne.

Nie sądzę też , że musisz uczyć się monad, aby pisać gry w (nieczystym) języku FP. Wynika to z faktu, że Twój kod najprawdopodobniej będzie blokował i jednowątkowy.

Jest to szczególnie prawdziwe, jeśli piszesz grę wieloosobową. Dzięki temu podejściu wszystko stanie się znacznie łatwiejsze, ponieważ pozwala na trywialną serializację stanu gry i wysyłanie go przez sieć.

Co do tego, dlaczego więcej gier nie jest napisanych w ten sposób ... Nie mogę powiedzieć. Jest to jednak prawdą we wszystkich domenach programistycznych (z wyjątkiem być może finansów), dlatego nie użyłbym tego jako argumentu, że języki funkcjonalne są nieodpowiednie dla programowania gier.

Warto również przeczytać Czysto funkcjonalne Retrogames .


1

To, czego szukasz, to tworzenie gier FRP.

Niektóre prezentacje wideo:

Jest w 100% możliwe i lepiej, aby logika gry podstawowej była czysto funkcjonalna, przemysł jako całość jest po prostu opóźniony, utknął w jednym paradygmacie myślenia.

Można to zrobić również w Jedności.

Aby odpowiedzieć na pytanie, nowy stan gry będzie aktualizowany / tworzony za każdym razem, gdy coś się poruszy, jak mówi Carmel w swoim wystąpieniu, nie jest to problemem. Drastyczne zmniejszenie ogólnych kosztów poznawczych, które wynika z czysto funkcjonalnej, łatwej w utrzymaniu, elastycznej architektury, która znacznie przewyższa wydajność, o ile w ogóle istnieje.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.