我正在尝试使用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)。

最佳答案

在伪代码中,合并的作用是:

firstStream.Subscribe(...);
secondStream.Subscribe(...);


传递给Observable.create的订阅函数是同步的,不会将控制权交还给调用者。这意味着merge本身被阻止尝试订阅displayStream之后的任何流。当对流进行重新排序以使displayStream首先出现时,可以防止其订阅KeyPressed流。这就是为什么您看到自己的行为的原因。

在某些方面,您的displayStream行为不佳。 Subscribe方法不应阻止。

因此,请确保displayStream是列表中的最后一项,或者对代码进行一些重构。您可以将Subject用作displayStream。然后订阅所有内容,最后启动“显示循环”,在其中执行当前在您的displayStream定义中的循环,并且每次循环时,只需在主题上调用OnNext

10-07 13:15