Programmation fonctionnelle pure et état du jeu

12

Existe-t-il une technique courante pour gérer l'état (en général) dans un langage de programmation fonctionnel? Il existe des solutions dans chaque langage de programmation (fonctionnel) pour gérer l'état global, mais je veux éviter cela autant que possible.

Tous les états d'une manière purement fonctionnelle sont des paramètres de fonction. J'ai donc besoin de mettre tout l'état du jeu (une gigantesque table de hachage avec le monde, les joueurs, les positions, le score, les actifs, les ennemis, ...)) comme paramètre pour toutes les fonctions qui veulent manipuler le monde sur une entrée ou un déclencheur donné . La fonction elle-même sélectionne les informations pertinentes dans le blob gamestate, fait quelque chose avec, manipule le gamestate et renvoie le gamestate. Mais cela ressemble à une mauvaise solution pour le problème. Si je mets le gamestate entier dans toutes les fonctions, il n'y a aucun avantage pour moi contrairement aux variables globales ou à l'approche impérative.

Je pourrais mettre juste les informations pertinentes dans les fonctions et retourner les actions qui seront prises pour l'entrée donnée. Et une seule fonction applique toutes les actions au gamestate. Mais la plupart des fonctions ont besoin de beaucoup d'informations «pertinentes». move()besoin de la position de l'objet, de la vitesse, de la carte de collision, de la position de tous les ennemis, de la santé actuelle, ... Donc cette approche ne semble pas fonctionner non plus.

Donc, ma question est de savoir comment gérer la quantité massive d'état dans un langage de programmation fonctionnel - en particulier pour le développement de jeux?

EDIT: Il existe des cadres de jeu pour créer des jeux dans Clojure. L'approche pour résoudre partiellement ce problème consiste à enfiler tous les objets du jeu en tant qu '"entités" et à les placer dans un énorme sac. Une fonction principale gigant tient l'écran et les entités et les événements de poignée ( :on-key-down, :on-init...) pour ce entités et exécuter la boucle d'affichage principal. Mais ce n'est pas la solution propre que je recherche.

Fu86
la source
Je pense à ce genre de choses depuis un moment; pour moi, ce n'est pas l' entrée qui est le problème unique, car vous devez toujours alimenter (à peu près) les mêmes éléments pour les fonctions de programmation non fonctionnelle. Non, c'est la sortie (et les mises à jour connexes ultérieures) qui est le problème. Certains de vos paramètres d'entrée doivent être combinés; car move(), vous devriez probablement passer dans l'objet `` actuel '' (ou un identifiant pour lui), plus le monde dans lequel il se déplace, et juste dériver la position et la vitesse actuelles ... la sortie est alors le monde physique entier, ou du moins une liste d'objets modifiés.
Clockwork-Muse
L'avantage de la fonctionnalité pure est que les prototypes de fonction montrent toutes les dépendances de votre programme.
tp1
3
IMO, les langages fonctionnels sont mal adaptés à l'écriture de jeux. C'est l'un des nombreux problèmes que vous devrez résoudre. Les jeux nécessitent un contrôle très précis des performances et ont rarement une bonne simultanéité, en raison de la manière imprévisible dont les événements se produisent naturellement. Les langages fonctionnels (purs) sont remarquables pour être trivialement parallélisables et difficiles à optimiser. Un jeu est DIFFICILE à écrire, et je recommande de le faire dans un langage typique, avant d'aborder quelque chose d'aussi complexe (programmation fonctionnelle).
Casey Kuball

Réponses:

7

Les effets secondaires et l'état dans les langages de programmation fonctionnels sont un problème plus large en informatique. Au cas où vous ne les auriez jamais rencontrés auparavant, jetez un œil aux monades . Soyez averti, cependant: c'est un concept assez avancé et la plupart des gens que je connais (moi y compris) ont du mal à les comprendre. Il existe de très nombreux didacticiels en ligne, avec des approches et des exigences de connaissances différentes. Personnellement, j'ai aimé le meilleur d'Eric Lippert.

Je suis un programmeur C # sans aucune expérience en "programmation fonctionnelle". Quelle est cette chose «monade» dont je n'arrête pas d'entendre parler, et à quoi cela me sert-il?

Eric Lippert sur Monads

Certaines choses à considérer, cependant:

  • Vous insistez pour utiliser un langage purement fonctionnel? Si vous êtes compétent à la fois en programmation fonctionnelle et en développement de jeux, vous pourriez peut-être réussir. (Même si j'aimerais savoir si les avantages en valent la peine.)
  • Ne serait-il pas préférable de n'utiliser l'approche fonctionnelle qu'en cas de besoin? Si vous utilisez un langage orienté objet (ou, plus probablement, multi-paradigme), rien ne vous empêche d'utiliser un style fonctionnel pour implémenter des sections qui en profitent. (Un peu comme MapReduce, peut-être?)

Quelques réflexions finales:

  • Parallélisme: Alors que les jeux ne l' utilisent beaucoup, autant que je sache la plus grande partie se passe déjà sur le GPU de toute façon.
  • Apatridie: les mutations d'État font partie intégrante des jeux. Essayer de s'en débarrasser pourrait simplement compliquer inutilement les choses.
  • Regardez peut-être comment le langage fonctionnel F # joue avec l'écosystème orienté objet de .NET, si cela vous intéresse.

Dans l'ensemble, je pense que même si cela pourrait être intéressant sur le plan académique, je doute que cette approche soit pratique et en vaille la peine.

ver
la source
Pourquoi publier un commentaire sur un sujet dans lequel vous n'avez aucune expérience? Une opinion venant de personnes prises au piège dans un paradigme de pensée.
Anthony Raimondo
4

J'ai écrit quelques jeux en F # (multi-paradigme, impur, langage fonctionnel en premier), avec des approches allant de la POO au FRP . C'est une vaste question, mais je ferai de mon mieux.

Existe-t-il une technique courante pour gérer l'état (en général) dans un langage de programmation fonctionnel?

Ma façon préférée est d'avoir un type immuable qui représente l'ensemble du jeu State. J'ai alors une référence mutable au courant Statequi est mis à jour à chaque tick. Ce n'est pas strictement pur, mais cela limite la mutabilité à un seul endroit.

Si je mets le gamestate entier dans toutes les fonctions, il n'y a aucun avantage pour moi contrairement aux variables globales ou à l'approche impérative.

Pas vrai. Parce que le Statetype est immuable, vous ne pouvez avoir aucun ancien composant mutant le monde de manière mal définie. Cela corrige le plus gros problème de l' GameObjectapproche (popularisé par Unity): il est difficile de contrôler l'ordre des Updateappels.

Et contrairement à l'utilisation des globaux, il est facilement testable et parallélisable,

Vous devez également écrire des fonctions d'assistance qui reçoivent des sous-propriétés de l'état pour résoudre le problème.

Par exemple:

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

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

Ici, updateagit sur l'État tout entier, mais updateSpaceShipn'agit que sur un individu SpaceShipisolé.

Je pourrais mettre juste les informations pertinentes dans les fonctions et retourner les actions qui seront prises pour l'entrée donnée.

Ma suggestion serait de créer un Inputtype contenant les états du clavier, de la souris, de la manette de jeu, etc. Vous pouvez ensuite écrire une fonction qui prend un Stateet un Inputretournant le suivant State:

let applyInput input state = 
  // Something

Pour vous donner une idée de la façon dont cela s'imbrique, le jeu global pourrait ressembler à ceci:

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

Donc, ma question est de savoir comment gérer la quantité massive d'état dans un langage de programmation fonctionnel - en particulier pour le développement de jeux?

Pour les jeux simples, vous pouvez utiliser l'approche ci-dessus (fonctions d'assistance). Pour quelque chose de plus compliqué, vous voudrez peut-être essayer des " lentilles ".

Contrairement à certains commentaires ici, je suggère fortement d'écrire des jeux dans un langage de programmation fonctionnel (impur), ou du moins de l'essayer! J'ai trouvé que cela rend l'itération plus rapide, les bases de code plus petites et les bogues moins courants.

Je ne pense pas non plus que vous ayez besoin d'apprendre des monades pour écrire des jeux dans un langage FP (impur). En effet, votre code sera très probablement bloquant et à thread unique.

Cela est particulièrement vrai si vous écrivez un jeu multijoueur. Ensuite, les choses deviendront beaucoup plus faciles avec cette approche, car elle vous permet de sérialiser de manière triviale l'état du jeu et de l'envoyer sur le réseau.

Quant à savoir pourquoi plus de jeux ne sont pas écrits de cette façon ... je ne peux pas le dire. Cependant, cela est vrai dans tous les domaines de programmation (sauf peut-être la finance), donc je n'utiliserais pas cela comme un argument selon lequel les langages fonctionnels sont mal adaptés à la programmation de jeux.

Les rétrogames purement fonctionnels valent également la peine d'être lus .

sdgfsdh
la source
1

Ce que vous cherchez, c'est le développement de jeux FRP.

Quelques introductions vidéo:

Il est 100% possible et préférable de rendre la logique du jeu de base d'une manière purement fonctionnelle, l'industrie dans son ensemble est tout simplement derrière, coincée dans un paradigme de pensée.

Il est également possible de le faire dans Unity.

Pour répondre à la question, un nouvel état du jeu sera mis à jour / créé chaque fois que quelque chose bouge, comme le dit carmack dans son exposé, ce n'est pas un problème. La réduction drastique des frais généraux cognitifs qui provient d'une architecture purement fonctionnelle, hautement maintenable et flexible est loin de nuire aux performances, si elle existe.

Anthony Raimondo
la source