纯函数式编程和游戏状态


12

在功能编程语言中,是否存在一种通用的技术来处理状态(通常)?每种(功能)编程语言都有解决全局状态的解决方案,但我想尽可能避免这种情况。

以纯功能方式的所有状态都是功能参数。因此,我需要将整个游戏状态(包含世界,玩家,位置,得分,资产,敌人等的巨大哈希图)作为要在给定输入或触发器上操纵世界的所有函数的参数。函数本身从游戏状态Blob中提取相关信息,对其进行处理,操纵游戏状态并返回游戏状态。但这似乎是解决这个问题的穷人解决方案。如果我将整个游戏状态放到所有函数中,那么与全局变量或命令式方法相比,对我没有任何好处。

我可以只将相关信息放入函数中,然后返回将为给定输入执行的操作。一个功能将所有动作应用于游戏状态。但是大多数功能都需要大量 “相关”信息。move()需要物体的位置,速度,碰撞图,所有敌人的位置,当前的健康状况……因此,这种方法似乎也不起作用。

所以我的问题是我该如何处理功能性编程语言中的大量状态-特别是对于游戏开发?

编辑:在Clojure中有一些用于构建游戏的游戏框架。部分解决此问题的方法是将游戏中的所有对象作为“实体”穿入并放入一个大袋子。一个吉甘特的主要功能是保持屏幕和实体和处理事件(:on-key-down:on-init这个实体,...),然后运行主显示屏循环。但这不是我要寻找的干净解决方案。


我一直在想这种事情。对我来说,输入不是唯一的问题,因为在非功能编程中,您仍然必须(大致)向功能中馈入相同的元素。不,这是问题的输出(以及后续的相关更新)。您的某些输入参数应组合在一起。对于move(),您应该传递的是“当前”对象(或它的标识符),再加上它正在通过的世界,然后仅得出当前位置和速度...那么输出就是整个物理世界,或者至少是更改对象的列表。
Clockwork-Muse

纯函数的优点是函数原型可以显示程序具有的所有依赖项。
tp1 2014年

3
IMO,功能语言不适合编写游戏。这是您需要解决的许多问题之一。游戏需要非常精确的性能控制,并且由于事件自然发生的方式无法预测,因此很少有良好的并发性。(纯)功能语言以微不足道的可并行性和难以优化而著称。游戏是很难编写的,我建议您以一种典型的语言来编写游戏,然后再进行一些复杂的事情(功能编程)。
Casey Kuball 2014年

Answers:


7

函数式编程语言中的副作用和状态是计算机科学中的一个更广泛的问题。如果您以前从未遇到过它们,也许看看monads。但是要注意:它们是一个相当高级的概念,我认识的大多数人(包括我在内)都难以掌握它们。在线上有很多教程,它们具有不同的方法和知识要求。就个人而言,我最喜欢Eric Lippert。

我是一名C#程序员,没有任何“函数式编程”背景。我不断听到的“单子”是什么,对我有什么用?

埃里克·利珀特(Eric Lippert)on Monads

但是,需要考虑一些事项:

  • 您是否坚持使用纯功能语言?如果您同时具备函数式编程和游戏开发的能力,则可以将其付诸实践。(即使我想知道这些好处是否值得付出努力。)
  • 仅在必要时使用功能方法会更好吗?如果您使用的是面向对象(或更可能是多范例)的语言,那么没有什么会阻止您使用功能样式来实现从中受益的部分。(也许金达喜欢MapReduce?)

最后的一些想法:

  • 并行性:尽管游戏确实大量使用它,但AFAIK大部分已经在GPU上发生了。
  • 无状态性:状态变化是游戏的组成部分。试图摆脱它们可能只会使事情不必要地复杂化。
  • 如果您感兴趣的话,也许可以看看功能语言F#如何与.NET的面向对象生态系统一起使用。

总而言之,我认为即使在学术上可能很有趣,但我仍怀疑这种方法是否实用且值得努力。


为什么要发表关于您没有经验的主题的评论?来自陷入一种思维范式的人们的观点。
Anthony Raimondo

4

我已经使用F#(多范式,不纯的,功能优先的语言)编写了一些游戏,方法从OOP到FRP。这是一个广泛的问题,但我会尽力而为。

在功能编程语言中,是否存在一种通用的技术来处理状态(通常)?

我的首选方式是拥有一个代表整个游戏的不可变类型State。然后,我可以对State每个刻度更新的当前内容进行可变引用。这不是严格意义上的纯净,但将可变性限制在一个地方。

如果我将整个游戏状态放到所有函数中,那么与全局变量或命令式方法相比,对我没有任何好处。

不对。因为State类型是不可变的,所以不能有任何旧的组件以不确定的方式改变世界。这解决了该GameObject方法的最大问题(由Unity推广):难以控制Update调用顺序。

而且与使用全局变量不同,它很容易进行单元测试和并行化,

您还应该编写帮助程序函数,以接收状态的子属性来解决问题。

例如:

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

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

这里update作用于整个状态,但updateSpaceShipSpaceShip孤立地作用于一个人。

我可以只将相关信息放入函数中,然后返回将为给定输入执行的操作。

我的建议是创建一个Input可以容纳键盘,鼠标,游戏手柄等状态的类型。然后,您可以编写一个接受a StateInput返回next 的函数State

let applyInput input state = 
  // Something

为了让您了解如何将它们组合在一起,整个游戏可能看起来像这样:

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

所以我的问题是我该如何处理功能性编程语言中的大量状态-特别是对于游戏开发?

对于简单的游戏,您可以使用上面的方法(辅助功能)。对于更复杂的事情,您可能想尝试“ 镜头 ”。

与这里的一些评论相反,我强烈建议您使用一种(不纯正的)功能编程语言来编写游戏,或者至少尝试一下!我发现它使迭代更快,代码库更小且错误更不常见。

也不认为您需要学习monad来用(不纯)FP语言编写游戏。这是因为您的代码很可能会被阻塞并且是单线程的。

如果您正在编写多人游戏,则尤其如此。然后,使用这种方法将使事情变得更加容易,因为它使您可以轻松地序列化游戏状态并通过网络发送。

至于为什么更多的游戏没有这样写...我不能说。但是,在所有编程领域(也许除了金融领域)都是如此,因此,我不会将其用作功能语言不适用于游戏编程的论点。

同样值得一读的是Purely Functional Retrogames


1

您需要的是FRP游戏开发。

一些视频介绍:

以纯功能性的方式制作核心游戏逻辑是100%可能的并且更可取的,整个行业完全落后,陷入一种思维范式。

也可以在Unity中做到这一点。

为了回答这个问题,就像卡马克在演讲中说的那样,每次移动时都会更新/创建一个新的游戏状态,这不是问题。纯粹的功能性,高度可维护性,灵活的体系结构可大大减少认知开销(如果存在的话)远远无法达到性能要求。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.