我正在玩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);
分数将在预期时递增,并在拾取角色时停止。然而,使测量仪行为正常工作一直很麻烦。我已经使用Window
,Generate
等尝试了大量的变体......但似乎最终发生的事情是它根本不起作用,或者增量/减量测量操作最终互相争斗,或者它们似乎都能正常工作,但继续在背景中减去或添加点/规格。这是规格实施(性能极差,大约10-15秒后崩溃,无法正常工作):
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缺乏了解在某种方式,形状或形式上存在缺陷,但我已经达到了尝试不同运算符/查询的极限。任何见解?
EPILOGUE:Gideon Engelberth的回答符合我的需求 - 我希望我可以将它推荐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 :(得分: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”observable,您可以通过递增方法触发它。 “gague empty”将触发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
为了测试应用程序,一切都在一个功能中。在您真实的应用程序中,您当然应该考虑要公开的内容以及如何使用。