Programação funcional pura e estado do jogo

12

Existe uma técnica comum para lidar com o estado (em geral) em uma linguagem de programação funcional? Existem soluções em todas as linguagens de programação (funcionais) para lidar com o estado global, mas quero evitar isso o máximo possível.

Todos os estados de uma maneira funcional pura são parâmetros de função. Então, preciso colocar todo o estado do jogo (um gigantesco mapa de hash com o mundo, jogadores, posições, pontuação, ativos, inimigos, ...)) como um parâmetro para todas as funções que desejam manipular o mundo em uma determinada entrada ou gatilho . A própria função seleciona as informações relevantes do blob gamestate, faz algo com ele, manipula o gamestate e retorna o gamestate. Mas isso parece uma solução pobre para o problema. Se eu colocar todo o estado do jogo em todas as funções, não há benefício para mim em contraste com as variáveis ​​globais ou a abordagem imperativa.

Eu poderia colocar apenas as informações relevantes nas funções e retornar as ações que serão tomadas para a entrada fornecida. E uma única função aplica todas as ações ao estado do jogo. Mas a maioria das funções precisa de muitas informações "relevantes". move()precisa da posição do objeto, da velocidade, do mapa para colisão, da posição de todos os inimigos, da saúde atual, ... Portanto, essa abordagem também não parece funcionar.

Então, minha pergunta é: como eu manejo a enorme quantidade de estado em uma linguagem de programação funcional - especialmente para o desenvolvimento de jogos?

EDIT: Existem algumas estruturas de jogos para a criação de jogos no Clojure. Uma abordagem para resolver esse problema parcialmente é enfiar todos os objetos no jogo como "entidades" e colocá-lo em um saco enorme. A função principal gigant está segurando a tela e as entidades e eventos alça ( :on-key-down, :on-init...) para esse entidades e executar o loop principal do display. Mas esta não é a solução limpa que estou procurando.

Fu86
fonte
Eu estive pensando sobre esse tipo de coisa há um tempo; para mim, não é a entrada que é o problema único, pois você ainda precisa alimentar (aproximadamente) os mesmos elementos para funções em programação não funcional. Não, é a saída (e atualizações relacionadas subseqüentes) que é o problema. Alguns de seus parâmetros de entrada devem ser combinados; pois move(), você provavelmente deveria estar passando o objeto 'atual' (ou um identificador para ele), mais o mundo pelo qual ele está se movendo e apenas derivando a posição e a velocidade atuais ... a saída é então o mundo inteiro da física, ou pelo menos uma lista de objetos alterados.
Clockwork-Muse
A vantagem do funcional puro é que os protótipos de funções mostram todas as dependências do seu programa.
tp1
3
Na IMO, as linguagens funcionais são pouco adequadas para escrever jogos. Esse é um dos muitos problemas que você precisará resolver. Os jogos exigem um controle muito preciso do desempenho e raramente apresentam uma boa simultaneidade, devido à maneira imprevisível que os eventos ocorrem naturalmente. As linguagens funcionais (puras) são notáveis ​​por serem trivialmente paralelelizáveis ​​e difíceis de otimizar. Um jogo é DIFÍCIL de escrever, e eu recomendo fazê-lo em uma linguagem típica, antes de assumir algo igualmente complexo (programação funcional).
Casey Kuball

Respostas:

7

Efeitos colaterais e estado em linguagens de programação funcionais são um problema mais amplo na ciência da computação. Caso você não os tenha encontrado antes, talvez dê uma olhada nas mônadas . Porém, esteja avisado: eles são um conceito bastante avançado e a maioria das pessoas que conheço (inclusive eu) luta para entendê-los. Existem muitos tutoriais on-line, com diferentes abordagens e requisitos de conhecimento. Pessoalmente, gostei do melhor de Eric Lippert.

Eu sou um programador de C # sem nenhuma experiência em "programação funcional". O que é essa coisa de “mônada” que eu continuo ouvindo e qual é a utilidade para mim?

Eric Lippert em Monads

Algumas coisas a considerar, no entanto:

  • Você insiste em usar uma linguagem puramente funcional? Se você é habilidoso em programação funcional e desenvolvimento de jogos, talvez seja possível. (Mesmo que eu gostaria de saber se os benefícios valem o esforço.)
  • Não seria melhor usar a abordagem funcional somente quando necessário? Se você estiver usando uma linguagem orientada a objetos (ou, mais provavelmente, com vários paradigmas), nada o impedirá de usar o estilo funcional para implementar seções que lucram com isso. (Como o MapReduce, talvez?)

Algumas considerações finais:

  • Paralelismo: Enquanto os jogos não usá-lo fortemente, AFAIK maior parte já acontece na GPU de qualquer maneira.
  • Apatridia: mutações de estado são parte integrante dos jogos. Tentar se livrar deles pode complicar as coisas desnecessariamente.
  • Talvez veja como a linguagem funcional F # brinca com o ecossistema orientado a objetos do .NET, se você estiver interessado.

Em suma, acho que, mesmo que possa ser interessante academicamente, duvido que essa abordagem seja prática e valha o esforço.

ver
fonte
Por que postar um comentário sobre um assunto em que você não tem experiência? Uma opinião vinda de pessoas presas em um paradigma de pensamento.
Anthony Raimondo
4

Escrevi alguns jogos usando F # (linguagem multiparadigma, impura, funcional primeiro), com abordagens que variam de OOP a FRP . Essa é uma pergunta ampla, mas farei o meu melhor.

Existe uma técnica comum para lidar com o estado (em geral) em uma linguagem de programação funcional?

Minha maneira preferida é ter um tipo imutável que represente o jogo inteiro State. Em seguida, tenho uma referência mutável à corrente Stateque é atualizada a cada marca. Isso não é estritamente puro, mas mantém a mutabilidade limitada a um lugar.

Se eu colocar todo o estado do jogo em todas as funções, não há benefício para mim em contraste com variáveis ​​globais ou com a abordagem imperativa.

Não é verdade. Como o Statetipo é imutável, você não pode ter nenhum componente antigo mutando o mundo de maneiras mal definidas. Isso corrige o maior problema da GameObjectabordagem (popularizada pelo Unity): é difícil controlar a ordem das Updatechamadas.

E, ao contrário do uso de globais, é facilmente testável por unidade e paralelamente,

Você também deve escrever funções auxiliares que recebem subpropriedades do estado para quebrar o problema.

Por exemplo:

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

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

Aqui updateatua em todo o estado, mas updateSpaceShipsomente em um indivíduo SpaceShipisoladamente.

Eu poderia colocar apenas as informações relevantes nas funções e retornar as ações que serão tomadas para a entrada fornecida.

Minha sugestão seria criar um Inputtipo que contenha os estados de teclado, mouse, gamepad etc. Em seguida, você pode escrever uma função que recebe um Statee um Inputretorno no próximo State:

let applyInput input state = 
  // Something

Para lhe dar uma idéia de como isso se encaixa, o jogo geral pode ser algo como isto:

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

Então, minha pergunta é: como eu manejo a enorme quantidade de estado em uma linguagem de programação funcional - especialmente para o desenvolvimento de jogos?

Para jogos simples, você pode usar a abordagem acima (funções auxiliares). Para algo mais complicado, você pode tentar " lentes ".

Ao contrário de alguns dos comentários aqui, eu sugiro escrever jogos em uma linguagem de programação funcional (impura), ou pelo menos tentar! Eu descobri que torna a iteração mais rápida, as bases de código menores e os bugs menos comuns.

Também não acho que você precise aprender mônadas para escrever jogos em uma linguagem FP (impura). Isso ocorre porque seu código provavelmente estará bloqueando e com thread único.

Isto é particularmente verdade se você estiver escrevendo um jogo multiplayer. Então, as coisas ficarão muito mais fáceis com essa abordagem, pois permite serializar trivialmente o estado do jogo e enviá-lo pela rede.

Quanto ao porquê de mais jogos não serem escritos dessa maneira ... não sei dizer. No entanto, isso é verdade em todos os domínios de programação (exceto talvez finanças), então eu não usaria isso como argumento de que linguagens funcionais são inadequadas para a programação de jogos.

Também vale a pena ler os Retrogames Puramente Funcionais .

sdgfsdh
fonte
1

O que você está procurando é o desenvolvimento de jogos de FRP.

Algumas introduções em vídeo:

É 100% possível e preferível tornar a lógica do jogo principal de uma maneira puramente funcional, a indústria como um todo está simplesmente atrasada, presa em um paradigma de pensamento.

É possível fazê-lo também no Unity.

Para responder à pergunta, um novo estado do jogo será atualizado / criado toda vez que algo se mover, como carmack diz em sua palestra, não é um problema. A redução drástica na sobrecarga cognitiva resultante de uma arquitetura puramente funcional, altamente sustentável e muito distante do desempenho atingido, se é que existe.

Anthony Raimondo
fonte