在计时器上生成事件时在值之间切换

编程入门 行业动态 更新时间:2024-10-23 05:39:28
本文介绍了在计时器上生成事件时在值之间切换的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

限时送ChatGPT账号..

我正在尝试在 XNA 概念验证中使用 Rx,并且在编写一些查询时遇到了一些障碍,我希望你们能帮助我理解其中一些问题操作员工作.

I'm playing with using the Rx in an XNA proof-of-concept, and I've run into a bit of an obstacle composing some queries that I'm hoping you folks can assist me with understanding how some of these operators work.

在我的 POC 中,我希望玩家的分数仅在没有发生主动拖动操作时增加.此外,还有一个抓斗",我想在有持续阻力时消耗它,并在没有阻力时填充.最后,如果正在进行拖动操作并且抓取仪表降至 0 以下,我想取消拖动操作.

In my POC, I would like the player's score to increment only while there is not an active drag operation occurring. In addition, there is a 'grab gauge' that I would like to deplete whenever there is an ongoing drag, and fill whenever there isn't. Finally, if a drag operation is under way and the grab gauge drops below 0, I want to cancel the drag operation.

我的分数递增工作得很好:

I've got the score incrementing working just fine with this:

 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-15 秒后崩溃,无法正常工作):

The score will increment when it is supposed to, and stop when the character is picked up. Getting the gauge behavior to work correctly has been troublesome however. I've tried a ton of variations using Window, Generate, etc... but what seems to end up happening is that either it doesn't work at all, or the increment/decrement gauge operations end up fighting each other, or it will all seem to work properly, but continue to subtract or add points/gauge in the background. Here's the gauge implementation (extremely poor performance, crashes after about 10-15s, doesn't work properly):

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 的缺乏理解在某种方式、形状或形式上有问题,但我已经达到了尝试不同运算符/查询的极限.有什么见解吗?

I have no doubts that my lack of understanding with Rx is at fault in some way, shape, or form, but I've reached the limit of experimenting with different operators/queries. Any insights?

EPILOGUE:Gideon Engelberth 的回答完全符合我的需求——我希望我能投票 10 倍!这是他的答案的快速 C# 表示(在 IDisposable.Dispose() 上不是 100%,但应该接近):

EPILOGUE: Gideon Engelberth's answer fit my needs spot-on - I wish I could upvote it 10x! Here's the quick C# representation of his answer (not 100% on the IDisposable.Dispose(), but should be close):

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
}

以及连接游戏类中事件的逻辑(有一些重构机会).总结:像魅力一样工作!谢谢!

And the logic to hook up the events in the game class (there are some refactoring opportunities). Summary: works like a charm! Thanks!

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

推荐答案

您绝对是在正确的轨道上.我将首先制作自己的抓取和发布可观察对象,然后根据这两个可观察对象制作 PrincessGrabbed.对于这样的情况,我使用了一个名为 AlternatingSubject 的类.

You are definitely on the right track. I would start by making grabs and releases observables of their own and then making PrincessGrabbed based on those two observables. For a case like this, I use a class I call 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 的释放部分.

Along with that, you will probably want to add a "gague full" observable you can trigger from the incrementing method. The "gague empty" will trigger the release portion of the 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

为了测试应用程序,一切都在一个功能中.在您的实际应用中,您当然应该考虑要公开什么以及如何公开.

For the sake of the test app, everything is in one function. In your real app, you should of course consider what to expose and how.

这篇关于在计时器上生成事件时在值之间切换的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

更多推荐

[db:关键词]

本文发布于:2023-05-01 12:20:20,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1409399.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:器上   事件

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!