斯卡拉游戏编程:以功能风格推进对象位置

jtb*_*jtb 13 functional-programming scala

很长一段时间java程序员慢慢学习scala(顺便爱上它),我认为我的思想仍然围绕着功能性写东西的概念.现在我正在尝试为一些移动的2D纹理编写一个简单的可视化器.命令式方法很简单,我相信大多数人都会认识到这个相对无处不在的代码块(改变了东西以保护无辜者):

class MovingTexture(var position, var velocity) extends Renders with Advances {
    def render : Unit = {...}
    def advance(milliseconds : Float) : Unit = {
        position = position + velocity * milliseconds
    }
}
Run Code Online (Sandbox Code Playgroud)

这段代码可以正常工作,但它有大量的可变状态,其功能充满了副作用.我不能让自己逃脱这一点,必须有更好的方法!

有没有人对这个简单的问题有一个惊人,优雅,实用的解决方案?有谁知道我可以学到更多解决这些问题的来源?

Dav*_*ith 14

这个答案的方式多于一个stackoverflow响应的空间,但对这类问题的最佳和最完整的答案是使用一种称为功能反应式编程的东西.基本思想是将每个时变或交互量表示为不是可变变量,而是表示不可变的值流,每个时间量一个.然后诀窍是,虽然每个值都是由可能无限的值流表示,但是流被懒惰地计算(以便在需要之前不占用内存),并且不会在时间量子中查看流值.过去(以便之前的计算可能是垃圾收集).计算功能很好且不可变,但您"看"的计算部分会随着时间而变化.

这一切都相当复杂,像这样组合流是很棘手的,特别是如果你想避免内存泄漏并让一切都以线程安全和有效的方式工作.有一些Scala库实现了功能反应式编程,但成熟度还不是很高.最有趣的可能是scala.react,在这里描述.


Rex*_*err 11

游戏通常是高性能事务,在这种情况下,您可能会发现可变状态就是您需要的东西.

但是,在这种情况下,有几个简单的解决方案:

case class MovingTexture(position: VecXY, velocity: VecXY) extends Renders with Advances {
  def advance(ms: Float) = copy(position = position + ms*velocity
  def accelerate(acc: Float, ms: Float) = copy(velocity = velocity + ms*acc)
  ...
}
Run Code Online (Sandbox Code Playgroud)

也就是说,让他们自己更新自己的副本,而不是让你的类自我更新.(你可以看到这可能会很快变得昂贵.对于俄罗斯方块来说,没什么大不了的.对于孤岛危机?也许不那么聪明.)这似乎只是将问题推回到一个层面:现在你需要一个var,对MovingTexture吧?一点也不:

Iterator.iterate(MovingTexture(home, defaultSpeed))(_.advance(defaultStep))
Run Code Online (Sandbox Code Playgroud)

这将在同一方向产生无穷无尽的位置更新.你可以做更复杂的事情来混合用户输入或诸如此类的东西.

或者,你可以

class Origin extends Renders {
  // All sorts of expensive stuff goes here
}
class Transposed(val ori: Origin, val position: VecXY) extends Renders with Advances {
  // Wrap TextureAtOrigin with inexpensive methods to make it act like it's moved
  def moving(vel: VecXY, ms: Float) = {
   Iterator.iterate(this).(tt => new Transposed(tt.ori, position+ms*vel))
  }
}
Run Code Online (Sandbox Code Playgroud)

也就是说,重量级的东西永远不会更新,并且具有较轻的视图,使它们看起来好像它们改变了你想要它们改变的方式.