2012-02-23 85 views
0

我在XNA概念證明中使用Rx,並且遇到了一些障礙,它們組成了一些查詢,我希望你們可以幫助我理解這些操作員中的一些如何工作。在定時器上產生事件時切換值

在我的POC中,我希望玩家的分數只在沒有發生主動拖動操作的情況下增加。另外,有一個'搶規',我想消耗,每當有一個持續的拖動,並填充時,沒有。最後,如果正在進行拖動操作,並且抓鬥的高度低於0,我想取消拖動操作。

我已經得到了分數遞增工作得很好這一點:當它應該

IObservable<bool> PrincessGrabbed; // e.g., OnGrabbedBegin 
_playerScoreChanged = IObservable<Unit> 
// ... // 

// In the initialization method 
_playerScoreChanged = from startTrigger in PrincessGrabbed.StartWith(false) 
                  .Where(x => !x) 
         from i in Observable.Interval(TargetElapsedTime) 
              .TakeUntil(PrincessGrabbed 
                 .Where(x => x) 
         select new Unit(); 

_playerScoreChanged.Subscribe(unit => PlayerScore += 1); 

分數將增加,而當人物被選中了停止。然而,讓量表行爲正常工作一直很麻煩。我已經嘗試了一大堆使用WindowGenerate等的變體......但是最終發生的結果是,要麼它根本不起作用,要麼增量/減量量表操作最終會相互爭鬥,或者它將似乎都能正常工作,但繼續在背景中減去或添加點/量表。這裏的計實現(表現極差,經過約10-15s崩潰,無法正常工作):

var a = from startTrigger in PrincessGrabbed.StartWith(false).Where(x => x) 
       from i in Observable.Interval(TargetElapsedTime) 
        .Where(x => GrabGaugeFillAmount > 0) 
        .TakeUntil(PrincessGrabbed.Where(x => !x)) 
       select new Unit(); 

a.TimeInterval().Subscribe(unit => 
      GrabGaugeFillAmount -= (float)unit.Interval.TotalSeconds * 
       GrabGaugeDepletionPerSecond); 

我毫不懷疑我缺乏的,其中Rx的理解是錯在某種程度上,形狀,或形式,但我已經達到了試驗不同運營商/查詢的極限。任何見解?


專題:Gideon Engelberth的答案符合我的需求點 - 我希望我可以upvote它10倍!下面是他的回答快C#表示(不是100%對IDisposable.Dispose(),而應該是接近):

public class AlternatingSubject : IDisposable 
{ 
    private readonly object _lockObj = new object(); 

    private int _firstTriggered; 

    private readonly ISubject<Unit> _first = new Subject<Unit>(); 
    public ISubject<Unit> First { get { return _first; }} 

    private readonly ISubject<Unit> _second = new Subject<Unit>(); 
    public ISubject<Unit> Second { get { return _second; }} 

    public void TriggerFirst() 
    { 
     if (System.Threading.Interlocked.Exchange(ref _firstTriggered, 1) == 1) 
      return; 

     First.OnNext(Unit.Default); 
    } 

    public void TriggerSecond() 
    { 
     if (System.Threading.Interlocked.Exchange(ref _firstTriggered, 0) == 0) 
      return; 

     Second.OnNext(Unit.Default); 
    } 

    #region Implementation of IDisposable 
    public void Dispose() 
    { 
     lock (_lockObj) 
     { 
      First.OnCompleted(); 
      Second.OnCompleted(); 
     } 
    } 
    #endregion 
} 

和邏輯掛鉤在遊戲類的事件(有一些重構機會)。總結:作品像魅力!謝謝!

public class PrincessCatcherGame : Game 
{ 
    // ... // 
    public IObservable<bool> PrincessGrabbed // external source fires these events 
    { 
     get 
     { 
      return princessGrabbed.AsObservable(); 
     } 
    } 
    // ... // 
    private readonly ISubject<bool> _princessGrabbed = new Subject<bool>(); 
    private readonly ISubject<Unit> _grabGaugeEmptied = new Subject<Unit>(); 
    private readonly ISubject<Unit> _grabGaugeFull = new Subject<Unit>(); 
    private readonly AlternatingSubject _alternatingSubject = new AlternatingSubject(); 
    private ISubject<Unit> _grabs; 
    private ISubject<Unit> _releases; 
    // ... // 
    private void SubscribeToGrabbedEvents() 
    { 
     var decrements = from g in _grabs 
         from tick in Observable.Interval(TargetElapsedTime).TakeUntil(_releases) 
         select Unit.Default; 
     decrements.Subscribe(x => 
           { 
            Debug.Assert(GrabGaugeFillAmount >= 0); 
            GrabGaugeFillAmount -= (GrabGaugeDepletionPerSecond/30f); 
            if (GrabGaugeFillAmount <= 1) 
            { 
             GrabGaugeFillAmount = 0; 
             _alternatingSubject.TriggerSecond(); 
             _grabGaugeEmptied.OnNext(Unit.Default); 
            } 
           }); 
     decrements.Subscribe(x => PlayerScore += 1); 

     var increments = from r in _releases 
         from tick in Observable.Interval(TargetElapsedTime).TakeUntil(_grabs.Merge(_grabGaugeFull)) 
         select Unit.Default; 
     increments.Subscribe(x => 
           { 
            Debug.Assert(GrabGaugeFillAmount <= 100); 
            GrabGaugeFillAmount += (GrabGaugeFillPerSecond/30f); 
            if (GrabGaugeFillAmount >= 100) 
            { 
             GrabGaugeFillAmount = 100; 
             _grabGaugeFull.OnNext(Unit.Default); 
            } 
           }); 
    } 
+0

關於配置,我其實只是處理這兩個主題。大多數處置最終只是「處置所有非空的一次性域」,這就是爲什麼我將其排除在外。 – 2012-04-04 16:08:22

回答

1

你絕對是在正確的軌道上。我會先抓住並釋放他們自己的觀察對象,然後根據這兩個可觀察結果製作PrincessGrabbed。對於這樣的情況,我使用我稱之爲AlternatingSubject的課程。

Public NotInheritable Class AlternatingSubject 
    Implements IDisposable 
    'IDisposable implementation left out for sample 

    Private _firstTriggered As Integer 

    Private ReadOnly _first As New Subject(Of Unit)() 
    Public ReadOnly Property First As IObservable(Of Unit) 
     Get 
      Return _first 
     End Get 
    End Property 

    Private ReadOnly _second As New Subject(Of Unit)() 
    Public ReadOnly Property Second As IObservable(Of Unit) 
     Get 
      Return _second 
     End Get 
    End Property 

    Public Sub TriggerFirst() 
     If System.Threading.Interlocked.Exchange(_firstTriggered, 1) = 1 Then Exit Sub 
     _first.OnNext(Unit.Default) 
    End Sub 

    Public Sub TriggerSecond() 
     If System.Threading.Interlocked.Exchange(_firstTriggered, 0) = 0 Then Exit Sub 
     _second.OnNext(Unit.Default) 
    End Sub 

End Class 

除此之外,您可能還想添加一個「gague full」可觀察值,您可以從增量方法中觸發。 「空白」將觸發AlternatingSubject的發佈部分。

Sub Main() 
    Dim alt As New AlternatingSubject 
    Dim grabs = alt.First 
    Dim releases = alt.Second 
    Dim isGrabbed As New Subject(Of Boolean)() 

    'I assume you have these in your real app, 
    'simulate them with key presses here 
    Dim mouseDowns As New Subject(Of Unit) 
    Dim mouseUps As New Subject(Of Unit) 

    Dim gagueFulls As New Subject(Of Unit)() 

    'the TakeUntils ensure that the timers stop ticking appropriately 
    Dim decrements = From g In grabs 
        From tick In Observable.Interval(TargetElapsedTime) _ 
            .TakeUntil(releases) 
        Select Unit.Default 
    'this TakeUnitl watches for either a grab or a gague full 
    Dim increments = From r In releases 
        From tick In Observable.Interval(TargetElapsedTime) _ 
            .TakeUntil(grabs.Merge(gagueFulls)) 
        Select Unit.Default 

    'simulated values for testing, you may just have 
    'these be properties on an INotifyPropertyChanged object 
    'rather than having a PlayerScoreChanged observable. 
    Const GagueMax As Integer = 20 
    Const GagueMin As Integer = 0 
    Const GagueStep As Integer = 1 
    Dim gagueValue As Integer = GagueMax 
    Dim playerScore As Integer 

    Dim disp As New CompositeDisposable() 
    'hook up IsGrabbed to the grabs and releases 
    disp.Add(grabs.Subscribe(Sub(v) isGrabbed.OnNext(True))) 
    disp.Add(releases.Subscribe(Sub(v) isGrabbed.OnNext(False))) 
    'output grabbed state to the console for testing 
    disp.Add(isGrabbed.Subscribe(Sub(v) Console.WriteLine("Grabbed: " & v))) 
    disp.Add(gagueFulls.Subscribe(Sub(v) Console.WriteLine("Gague full"))) 


    disp.Add(decrements.Subscribe(Sub(v) 
             'testing use only 
             If gagueValue <= GagueMin Then 
              Console.WriteLine("Should not get here, decrement below min!!!") 
             End If 

             'do the decrement 
             gagueValue -= GagueStep 
             Console.WriteLine("Gague value: " & gagueValue.ToString()) 
             If gagueValue <= GagueMin Then 
              gagueValue = GagueMin 
              Console.WriteLine("New gague value: " & gagueValue) 
              alt.TriggerSecond() 'trigger a release when the gague empties 
             End If 
            End Sub)) 
    disp.Add(decrements.Subscribe(Sub(v) 
             'based on your example, it seems you score just for grabbing 
             playerScore += 1 
             Console.WriteLine("Player Score: " & playerScore) 
            End Sub)) 
    disp.Add(increments.Subscribe(Sub(v) 
             'testing use only 
             If gagueValue >= GagueMax Then 
              Console.WriteLine("Should not get here, increment above max!!!") 
             End If 

             'do the increment 
             gagueValue += GagueStep 
             Console.WriteLine("Gague value: " & gagueValue.ToString()) 
             If gagueValue >= GagueMax Then 
              gagueValue = GagueMax 
              Console.WriteLine("New gague value: " & gagueValue) 
              gagueFulls.OnNext(Unit.Default) 'trigger a full 
             End If 
            End Sub)) 
    'hook the "mouse" to the grab/release subject 
    disp.Add(mouseDowns.Subscribe(Sub(v) alt.TriggerFirst())) 
    disp.Add(mouseUps.Subscribe(Sub(v) alt.TriggerSecond())) 

    'mouse simulator 
    Dim done As Boolean 
    Do 
     done = False 
     Dim key = Console.ReadKey() 
     If key.Key = ConsoleKey.G Then 
      mouseDowns.OnNext(Unit.Default) 
     ElseIf key.Key = ConsoleKey.R Then 
      mouseUps.OnNext(Unit.Default) 
     Else 
      done = True 
     End If 
    Loop While Not done 
    'shutdown 
    disp.Dispose() 
    Console.ReadKey() 
End Sub 

爲了測試應用程序,一切都在一個功能。在你的真實應用程序中,你當然應該考慮要公開什麼和如何。

+0

我喜歡交替主題的方法。我會給它一個去,並在這裏發佈它是如何變成的 – 2012-02-27 13:04:25

+1

好吧聖潔的frijole它是罕見的,只是放下一些東西,並只是'工作',但這就是發生了什麼!張貼在回答編輯 – 2012-02-27 13:44:51