2011-01-17 5 views
13

時間のかかるJavaプログラマはゆっくりとスカラーを学習しています(私はそれを愛しています)。今、私はいくつかの動く2次元テクスチャのためのシンプルなビジュアライザーを作成しようとしています。不可欠なアプローチは十分に単純である、と私はあなたのほとんどは(ものは無実を保護するために変更された)コードのこの比較的ユビキタスブロックを認識します確信している:スケーラゲームプログラミング:機能的なスタイルでオブジェクトの位置を前進させる

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

このコードはうまく動作します、しかし、それは持っています数多くの変化する状態とその機能は副作用でいっぱいです。私は自分でこれを取り除くことはできません、より良い方法でなければなりません!

誰もが、この単純な問題に対する驚くほどエレガントで機能的な解決策を持っていますか?誰かがこの種の問題を解決する方法についてもっと知ることができるソースを知っていますか?

答えて

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) 
    ... 
} 

は、彼らが自分自身の新しいコピーを返します:

ただし、この場合には、簡単な解決策がいくつかあります。 (テトリスの場合、クライシスについてはそれほど賢明ではないかもしれませんが)これはちょうど問題を1つ後ろに押し戻すようです:MovingTextureの場合はvarが必要ですか?まったくではありません:

Iterator.iterate(MovingTexture(home, defaultSpeed))(_.advance(defaultStep)) 

これは、同じ方向の位置更新の無限ストリームを生成します。あなたは、ユーザーの入力やその他のものに混在させるために、もっと複雑なことをすることができます。

また、あなたは

ある
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)) 
    } 
} 

は、ヘビー級のものが更新されていないと、彼らはあなたがそれらを変更したいの方法で変更したかのように彼らが見えるようにそれらの軽量な景色を眺めることができることがないことができます。

+1

一般的なコンセンサス(すべての回答の中で)は、現在の状態を表すオブジェクトのコピーを作成するだけのようです。これは正確には私が探していたものではありませんが、_this_答えは、(値の状態を表現するのではなく)値がどのように変化するかを表現するイテレーターを定義するなどの考えのための、本当に面白い食べ物をいくつか提示します。変数がどのように変化するか(微積分における積分のようなもの)を表現する何らかの種類の関数(または反復)として位置を表現するための何らかの方法を見つける。しかし、私にいくつかのクールなものを紹介してくれてありがとう! – jtb

14

これは、1つのstackoverflowレスポンスのスペースに収まる方法よりもはるかに優れていますが、このような質問に対する最も完全で完全な答えは、Functional Reactive Programmingと呼ばれるものを使用することです。基本的な考え方は、時間可変または対話型の量を可変変数ではなくむしろ不変の値のストリームとして表現することです。したがって、各値は潜在的に無限の値のストリームで表されますが、ストリームは遅れて計算されるため(メモリが必要になるまでメモリが使用されないように)、ストリームの値は過去の計算がガベージコレクションされるようにします。計算はうまく機能し、不変ですが、あなたが「見ている」計算の部分は時間とともに変化します。

これはすべて複雑で、このようなストリームを組み合わせるのは難しいです。特に、メモリリークを回避し、すべてがスレッドセーフで効率的な方法で動作するようにする場合は特にそうです。 Functional Reactive Programmingを実装するScalaライブラリがいくつかありますが、成熟度はあまり高くありません。最も興味深いのはおそらくscala.reactで、hereと記載されています。

+0

あなたはこの問題についてどう考えているかについては頭の上の釘を打ったと思います。あなたはあなたのことを知っているように聞こえますが、私はあなたにいくつかの簡単なコード例を提供してもらいたいと思いますが、正しいかもしれませんが、この問題はあまりにも広範であり、 – jtb

+0

さて、私は実際にインタラクティブな側面ではあまり仕事をしておらず、怒りの中でFRPを使ったことは一度もありません。読書を終え、理論を知っているが、決して実際にコードをカットすることはない。 –

+0

あなたがそれを言及する前にFRPについて聞いたことはありませんでした。だから少なくとも今私はもっと情報を得ています。興味深い論文を読んでいます。ありがとう! – jtb

1

ここでは、状態を直接変更するのではなくコピーを返すアプローチを使用している、私が取り組んでいるコードのサンプルを示します。このようなアプローチの良い点は、少なくともサーバー側では、トランザクション型のセマンティクスを簡単に実装できることです。更新中に何か問題が生じた場合は、一貫性のある状態で更新されたものがすべて残っていることは自明です。

以下のコードは私が取り組んでいるゲームサーバーからのものです。あなたがやっているのと似たようなことをしています。タイムスライスで動き回っているオブジェクトを追跡するためです。このアプローチは、Dave Griffithの示唆ほど魅力的ではありませんが、熟考のためにあなたの役に立つかもしれません。 Scalaではケースクラスについて

case class PosController(
    pos: Vector3 = Vector3.zero, 
    maxSpeed: Int = 90, 
    velocity: Vector3 = Vector3.zero, 
    target: Vector3 = Vector3.zero 
) { 
    def moving = !velocity.isZero 

    def update(elapsed: Double) = { 
     if (!moving) 
      this 
     else { 
      val proposedMove = velocity * elapsed 
      // If we're about to overshoot, then stop at the exact position. 
      if (proposedMove.mag2 > pos.dist2(target)) 
       copy(velocity = Vector3.zero, pos = target) 
      else 
       copy(pos = pos + proposedMove) 
     } 
    } 

    def setTarget(p: Vector3) = { 
     if (p == pos) 
      this 
     else { 
      // For now, go immediately to max velocity in the correct direction. 
      val direction = (p - pos).norm 
      val newVel = direction * maxSpeed 
      copy(velocity = direction * maxSpeed, target = p) 
     } 
    } 

    def setTargetRange(p: Vector3, range: Double) = { 
     val delta = p - pos 
     // Already in range? 
     if (delta.mag2 < range * range) 
      this 
     else { 
      // We're not in range. Select a spot on a line between them and us, at max range. 
      val d = delta.norm * range 
      setTarget(p - d) 
     } 
    } 

    def eta = if (!moving) 0.0 else pos.dist(target)/maxSpeed 
} 

一つの良いところは、彼らはあなただけのパラメータが変更されている、と他の人が同じ値を保持するに渡しyou--ためのコピー()メソッドを作成することです。ケースクラスを使用していない場合は、手作業でコードを記述できますが、クラスにある値を変更するたびにコピーメソッドを更新する必要があります。

リソースに関しては、本当に私にとって違いを生むのは、基本的に不変の状態を使用する以外に、アーランに何か時間を費やすことでした。私は2つのErlangの本を持っていて、すべての例を慎重に研究しました。 Erlangでいくつかのことをやってもらうと、不変のデータで作業するのがずっと楽になりました。

+0

奇妙なことに、Erlangは私の弱い脳がまだScalaを最初にやる作業をたくさん持っているにもかかわらず、私が(あるいは多分Haskellを)拾おうとしていた次の言語です。私はあなたの例が好きです!私はまだ、反復ごとにオブジェクトのコピーを作成するのではなく、変数がどのように変化するかを表現するより一般的な方法があるはずだと思います。あなたはvarとしてではなく、時間の経過とともにポジションの関数として位置を表現することを実験したことがありますか? – jtb

+0

私はしていません。私は俳優のためのすべての状態を複数のvalからなる単一のvar内に保つことから始めました。たとえば、位置コントローラは1つのvalであり、脳の状態は別のvalです。それらはすべて単一の状態変数にロールバックされます。1つを変更し、別のメソッドを変更し、場合によっては例外をスローするメソッドがあれば、3つのすべての3つのオペレーションが失敗するか、3つすべてが失敗し、その状態が保持されることが保証されます。私が時間の関数として表現していない理由は、目標位置が時々脳クラスによって、時にはプレイヤーによって制御されるからです。 – Unoti

+0

あなたは、まずスカラであなたの脳を包み込むことがたくさんあると述べました。私にとっては、ScalaがErlangよりはるかに難しいことを発見しました。他の人は違った感情を抱いていますが、Erlang vs. Scalaに関する学習プロセスの間、私はあなたの興味をそそるかもしれないhttp://tango11.com/news/scala-complexity-vs-erlang-complexity/のアイデアを書きました。 – Unoti

2

インタラクティブなアプリケーションをプログラミングするための純粋に機能的なアプローチについては、「プログラムを設計する方法」の著者による「世界をどのように設計するか」というパンフレットがあります。

基本的には、 "ワールド"(すべてのゲーム状態を含むデータ型)と "tick"(world型 - > world型)と "onkeypress"(key * world型 - >世界)。 「レンダリング」機能は、世界を取り込んでシーンを返し、それを「リアル」レンダラに渡します。

0

この短い一連の短い記事は、プログラミング上の問題を機能的に解決するための初心者のために私を助けました。ゲームはRetro(Pac Man)ですが、プログラマーはそうではありません。 http://prog21.dadgum.com/23.html

関連する問題