Reine Funktionsprogrammierung und Spielstatus

12

Gibt es eine übliche Technik, um Zustände (im Allgemeinen) in einer funktionalen Programmiersprache zu behandeln? Es gibt in jeder (funktionalen) Programmiersprache Lösungen für den globalen Zustand, aber ich möchte dies so weit wie möglich vermeiden.

Alle rein funktionalen Zustände sind Funktionsparameter. Ich muss also den gesamten Spielstatus (eine gigantische Hashmap mit der Welt, Spielern, Positionen, Punkten, Assets, Feinden, ...) als Parameter für alle Funktionen angeben, die die Welt mit einem bestimmten Eingang oder Trigger manipulieren möchten . Die Funktion selbst wählt die relevanten Informationen aus dem Gamestate-Blob aus, tut etwas damit, manipuliert den Gamestate und gibt den Gamestate zurück. Aber das scheint eine schlechte Lösung für das Problem zu sein. Wenn ich den gesamten Gamestate in alle Funktionen einbaue, habe ich im Gegensatz zu globalen Variablen oder dem imperativen Ansatz keinen Nutzen für mich.

Ich könnte nur die relevanten Informationen in die Funktionen einfügen und die Aktionen zurückgeben, die für die gegebene Eingabe ausgeführt werden. Und eine einzige Funktion wendet alle Aktionen auf den Gamestate an. Die meisten Funktionen benötigen jedoch viele "relevante" Informationen. move()brauche die Objektposition, die Geschwindigkeit, die Karte für die Kollision, die Position aller Feinde, die aktuelle Gesundheit, ... Also scheint dieser Ansatz auch nicht zu funktionieren.

Meine Frage ist also, wie ich mit der enormen Menge an Status in einer funktionalen Programmiersprache umgehen kann - insbesondere für die Spieleentwicklung?

BEARBEITEN: Es gibt einige Spiel-Frameworks zum Erstellen von Spielen in Clojure. Ein Ansatz, um dieses Problem teilweise zu lösen, besteht darin, alle Objekte im Spiel als "Entitäten" zu fädeln und in einen riesigen Beutel zu packen. A gigant Hauptfunktion hält den Bildschirm und die Entitäten und Griff Ereignisse ( :on-key-down, :on-init, ...) für diese Einheiten und die Hauptanzeigeschleife laufen. Aber das ist nicht die saubere Lösung, nach der ich suche.

Fu86
quelle
Ich habe eine Weile darüber nachgedacht. Für mich ist die Eingabe nicht das einzige Problem, da Sie den Funktionen in der nichtfunktionalen Programmierung immer noch (ungefähr) dieselben Elemente hinzufügen müssen. Nein, das Problem ist die Ausgabe (und die nachfolgenden zugehörigen Updates). Einige Ihrer Eingabeparameter sollten kombiniert werden. für move(), sollten Sie wahrscheinlich an der ‚aktuellen‘ Objekt werden vorbei (oder eine Kennung für sie), plus die Welt es bewegt sich durch, und nur herleiten aktuelle Position und Geschwindigkeit ... ausgegeben die gesamte Physik Welt dann, oder zumindest eine Liste der geänderten Objekte.
Clockwork-Muse
Der Vorteil von rein funktional ist, dass Funktionsprototypen alle Abhängigkeiten Ihres Programms anzeigen.
TP1
3
IMO, funktionale Sprachen eignen sich schlecht zum Schreiben von Spielen. Dies ist eines von vielen Problemen, die Sie lösen müssen. Spiele erfordern eine sehr genaue Steuerung der Leistung und weisen aufgrund der unvorhersehbaren Art und Weise, in der Ereignisse auf natürliche Weise auftreten, selten eine gute Parallelität auf. (Reine) Funktionssprachen zeichnen sich dadurch aus, dass sie trivial parallelisierbar und schwer zu optimieren sind. Ein Spiel ist SCHWER zu schreiben, und ich empfehle, es nur in einer typischen Sprache zu machen, bevor man sich mit etwas ebenso Komplexem befasst (funktionale Programmierung).
Casey Kuball

Antworten:

7

Nebenwirkungen und Zustand in funktionalen Programmiersprachen sind ein größeres Problem in der Informatik. Falls Sie ihnen noch nicht begegnet sind, schauen Sie sich vielleicht Monaden an . Seien Sie jedoch gewarnt: Es handelt sich um ein ziemlich fortschrittliches Konzept, und die meisten Menschen, die ich kenne (ich eingeschlossen) haben Mühe, sie zu erfassen. Es gibt viele, viele Online-Tutorials mit unterschiedlichen Ansätzen und Wissensanforderungen. Persönlich gefiel mir Eric Lipperts am besten.

Ich bin ein C # -Programmierer ohne jeglichen Hintergrund für „funktionale Programmierung“. Was ist diese "Monade", von der ich immer wieder höre, und was nützt es mir?

Eric Lippert über Monaden

Einige Dinge zu beachten:

  • Bestehen Sie darauf, eine rein funktionale Sprache zu verwenden? Wenn Sie sich sowohl mit funktionaler Programmierung als auch mit Spieleentwicklung auskennen, könnten Sie es vielleicht schaffen. (Auch wenn ich gerne wissen möchte, ob sich die Leistung lohnt.)
  • Wäre es nicht besser, nur dann einen funktionalen Ansatz zu verwenden, wenn dies erforderlich ist? Wenn Sie eine objektorientierte Sprache (oder wahrscheinlich eine Multi-Paradigma-Sprache) verwenden, hindert nichts Sie daran, einen funktionalen Stil zu verwenden, um Abschnitte zu implementieren, die davon profitieren. (Ein bisschen wie MapReduce, vielleicht?)

Einige abschließende Gedanken:

  • Parallelismus: Während Spiele Sie es verwenden schwer, AFAIK das meiste davon geschieht bereits auf der GPU sowieso.
  • Staatenlosigkeit: Staatsmutationen sind ein wesentlicher Bestandteil von Spielen. Der Versuch, sie loszuwerden, könnte die Sache nur unnötig komplizieren.
  • Schauen Sie sich bei Interesse vielleicht an, wie die Funktionssprache F # mit dem objektorientierten Ökosystem von .NET spielt.

Alles in allem denke ich, auch wenn es wissenschaftlich interessant sein könnte, bezweifle ich, dass dieser Ansatz praktisch ist und die Mühe wert ist.

ver
quelle
Warum sollten Sie einen Kommentar zu einem Thema veröffentlichen, in dem Sie noch keine Erfahrung haben? Eine Meinung von Menschen, die in einem Denkmuster gefangen sind.
Anthony Raimondo
4

Ich habe einige Spiele mit F # (Multi-Paradigma, unreine, funktionale Erstsprache) mit Ansätzen von OOP bis FRP geschrieben . Dies ist eine weit gefasste Frage, aber ich werde mein Bestes geben.

Gibt es eine übliche Technik, um Zustände (im Allgemeinen) in einer funktionalen Programmiersprache zu behandeln?

Mein bevorzugter Weg ist es, einen unveränderlichen Typ zu haben, der das gesamte Spiel repräsentiert State. Ich habe dann einen veränderlichen Verweis auf den Strom State, der bei jedem Tick aktualisiert wird. Dies ist nicht unbedingt rein, aber es hält Veränderlichkeit an einen Ort beschränkt.

Wenn ich den gesamten Gamestate in alle Funktionen einbaue, habe ich im Gegensatz zu globalen Variablen oder dem imperativen Ansatz keinen Nutzen für mich.

Nicht wahr. Da der StateTyp unveränderlich ist, können Sie keine alte Komponente haben, die die Welt auf schlecht definierte Weise mutiert. Dies behebt das größte Problem mit dem GameObjectvon Unity verbreiteten Ansatz: Es ist schwierig, die Reihenfolge der UpdateAnrufe zu steuern .

Und im Gegensatz zur Verwendung von Globals ist es einfach in der Einheit zu testen und zu parallelisieren.

Sie sollten auch Hilfsfunktionen schreiben, die Sub-Eigenschaften des Status erhalten, um das Problem zu lösen.

Beispielsweise:

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

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

Hier updatewirkt auf den ganzen Staat, aber updateSpaceShipnur auf einen Einzelnen SpaceShipin Isolation.

Ich könnte nur die relevanten Informationen in die Funktionen einfügen und die Aktionen zurückgeben, die für die gegebene Eingabe ausgeführt werden.

Mein Vorschlag wäre, einen InputTyp zu erstellen , der die Zustände Tastatur, Maus, Gamepad usw. enthält. Sie können dann eine Funktion schreiben, die ein braucht Stateund Inputdie nächste zurückgibt State:

let applyInput input state = 
  // Something

Um Ihnen eine Vorstellung davon zu geben, wie dies zusammenpasst, könnte das gesamte Spiel so aussehen:

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

Meine Frage ist also, wie ich mit der enormen Menge an Status in einer funktionalen Programmiersprache umgehen kann - insbesondere für die Spieleentwicklung?

Für einfache Spiele können Sie den obigen Ansatz verwenden (Hilfsfunktionen). Für etwas komplizierteres möchten Sie vielleicht " Linsen " ausprobieren .

Im Gegensatz zu einigen Kommentaren hier würde ich dringend empfehlen, Spiele in einer (unreinen) funktionalen Programmiersprache zu schreiben oder zumindest auszuprobieren! Ich habe festgestellt, dass dies die Iteration beschleunigt, die Codebasis verkleinert und Fehler seltener macht.

Ich glaube auch nicht, dass Sie Monaden lernen müssen, um Spiele in einer (unreinen) FP-Sprache zu schreiben. Dies liegt daran, dass Ihr Code höchstwahrscheinlich blockiert und Single-Threaded ist.

Dies gilt insbesondere, wenn Sie ein Multiplayer-Spiel schreiben. Dann wird es mit diesem Ansatz viel einfacher, da Sie den Spielstatus einfach serialisieren und über das Netzwerk senden können.

Warum mehr Spiele nicht so geschrieben sind, kann ich nicht sagen. Dies gilt jedoch für alle Programmierbereiche (außer vielleicht für Finanzen). Daher würde ich dies nicht als Argument dafür verwenden, dass funktionale Sprachen für die Programmierung von Spielen ungeeignet sind.

Lesenswert ist auch Purely Functional Retrogames .

sdgfsdh
quelle
1

Was Sie suchen, ist die Entwicklung von FRP-Spielen.

Einige Videoeinführungen:

Es ist zu 100% möglich und vorzuziehen, die Kernlogik des Spiels rein funktional zu gestalten. Die Branche als Ganzes ist einfach zurückgeblieben und steckt in einem Denkmuster.

Dies ist auch in Unity möglich.

Um die Frage zu beantworten, wird jedes Mal, wenn sich etwas bewegt, ein neuer Spielstatus aktualisiert / erstellt, wie Carmack in seinem Vortrag sagt, es ist kein Problem. Die drastische Reduzierung des kognitiven Overheads, der sich aus einer rein funktionalen, hochgradig wartbaren und flexiblen Architektur ergibt, steht in keinem Verhältnis zu den Auswirkungen auf die Leistung, wenn überhaupt.

Anthony Raimondo
quelle