我正在尝试使用F#中的Observables编写一个基本的“游戏循环”。基本上我将事件的基本输入流概念化为两个流合并在一起:用户按键(游戏仅使用键盘开始),以及游戏的常规刻度(例如,每秒60次)。
我的问题似乎源于这样一个事实:观察到的序列之一,即刻度,也是在Window上调用DispatchEvents()的循环,允许它处理其输入并触发按键事件,因此一个流事件实际上是由另一方驱动的,如果这是有道理的。这是代码:
open System;
open System.IO
open SFML.Window
open SFML.Graphics
open System.Reactive
open System.Reactive.Linq
open System.Diagnostics
type InputEvent =
| Tick of TimeSpan
| KeyPressed of Keyboard.Key
[<EntryPoint;STAThread>]
let main _ =
use window = new RenderWindow(VideoMode(640u, 480u), "GameWindow")
window.SetVerticalSyncEnabled(true)
let displayStream =
Observable.Create(
fun (observer:IObserver<TimeSpan>) ->
let sw = Stopwatch.StartNew()
while (window.IsOpen()) do
window.DispatchEvents() // this calls the KeyPressed event synchronously
window.Display() // this blocks until the next vertical sync
window.Clear()
observer.OnNext sw.Elapsed
sw.Restart()
observer.OnCompleted();
{ new IDisposable with member this.Dispose() = ()})
let onDisplay elapsedTime =
// draw game: code elided
let inputEvents = Observable.merge
(window.KeyPressed |> Observable.map (fun key -> KeyPressed(key.Code)))
(displayStream |> Observable.map (fun t -> Tick(t)))
use subscription =
inputEvents.Subscribe(fun inputEvent -> match inputEvent with
| Tick(t) -> onDisplay(t)
| KeyPressed(key) -> printfn "%A" key)
0
但是,如果我在Observable.merge中更改参数的顺序:
let inputEvents = Observable.merge
(displayStream |> Observable.map (fun t -> Tick(t)))
(window.KeyPressed |> Observable.map (fun key -> KeyPressed(key.Code)))
然后游戏呈现(onDisplay被调用),但我没有看到KeyPressed事件打印到控制台。这是为什么?
(如果你想知道什么是SFML,这里是link)。
答案 0 :(得分:6)
在伪代码中,合并的作用是:
firstStream.Subscribe(...);
secondStream.Subscribe(...);
传递给Observable.create
的订阅功能是同步的,永远不会将控制权交还给调用者。这意味着merge
本身无法尝试订阅displayStream
之后的任何流。当您重新排序流以使displayStream
成为第一个时,您可以阻止它订阅您的KeyPressed
流。这就是你看到你所看到的行为的原因。
在某些方面,您的displayStream
表现不佳。 Subscribe
方法不应该阻止。
因此,要么确保displayStream
是列表中的最后一项,要么对代码进行一些重构。您可以Subject
使用displayStream
。然后订阅所有内容,最后启动&#34;显示循环&#34;,执行当前在displayStream
定义中的循环,每次循环,只需调用OnNext
主题。