1

So I wanted to use UniRx in Unity to try reactive programming. I gave myself a simple task. Have a stream for key presses for W A S D keys, print to the screen whenever any key is pressed, and display a Combi happened whenever the sequence WASD happens.

The problem is that if the sequence appears EXACTLY at the beginning of the stream then it is not "recognized" and no value gets emitted.

Here is what I mean: Here W A S D is pressed first.. first combination is not recognized, but all subsequent ones are. No event

Here I press a different letter at the beginning then do the pattern, everything works as intended. No problem

Here is the code:

public class TestController : MonoBehaviour {

    // Use this for initialization
    void Start(){
        var tick = this.UpdateAsObservable();
        var w = tick.Where(_ => Input.GetKeyDown(KeyCode.W)).Select(_ => "W");
        var s = tick.Where(_ => Input.GetKeyDown(KeyCode.S)).Select(_ => "S");
        var d = tick.Where(_ => Input.GetKeyDown(KeyCode.D)).Select(_ => "D");
        var a = tick.Where(_ => Input.GetKeyDown(KeyCode.A)).Select(_ => "A");

        w.Subscribe(Debug.Log).AddTo(this);
        s.Subscribe(Debug.Log).AddTo(this);
        d.Subscribe(Debug.Log).AddTo(this);
        a.Subscribe(Debug.Log).AddTo(this);

        var keys = Observable.Merge(w, a, s, d);

        var Combi = new[]{"W", "A", "S", "D"};
        var combiFound = keys.SelectMany(keys.Take(4).Buffer(4))
            .Where(list => list.SequenceEqual(Combi));

        combiFound.Subscribe(_ => { Debug.LogWarning("Combi Happened!"); }, Debug.LogException).AddTo(this);
    }
}

What exactly is happening here?

Mehdi Saffar
  • 691
  • 1
  • 8
  • 24

2 Answers2

1

I'm no reactive expert but I think what explains it is the SelectMany. There you say that only with multiple items extracted from the buffer the observable is filled with an item so subscribe is called. I changed it to this crumby fix so at least it will log from the first try but it's not elegant. I would like to see someone do it one statement.

    var Combi = new[] { "W", "A", "S", "D" };
    var buffer = keys.Take(4).Buffer(4);
    var combiFound = keys.SelectMany(buffer)
          .Where(list => list.SequenceEqual(Combi));

    buffer.Subscribe(CombiHappend(), Debug.LogException).AddTo(this);
    combiFound.Subscribe(CombiHappend(), Debug.LogException).AddTo(this);
}

private static System.Action<IList<string>> CombiHappend()
{
    return (items) => { Debug.LogWarning("Combi Happened!" + string.Join(",", items.ToArray())); };
}
Casper Broeren
  • 760
  • 2
  • 10
  • 27
  • Hi Casper, thank you for your reply. I tried your code and it works as I intend it to do. I am not sure I understand what you are saying about SelectMany here.. why do we have to subscribe to `buffer`? How does that make the observable sort of.. "awake" from the start? And also I noticed that whenever a combi happens, only one warning is written, not two.. but when I see the two `Subscribe`s on buffer and combiFound, I am kinda expecting two warnings? – Mehdi Saffar Feb 01 '18 at 21:54
1

I am very happy to have found this quick tutorial demo: Link to solution It is exactly what I am trying to achieve!

So here is the working code:

// Use this for initialization
private void Start(){
    var keyCode = new[]{"W", "A", "S", "D"};
    var updateTick = this.UpdateAsObservable();
    // Get a stream for each key
    var w = updateTick.Where(_ => Input.GetKeyDown(KeyCode.W)).Select(_ => "W");
    var s = updateTick.Where(_ => Input.GetKeyDown(KeyCode.S)).Select(_ => "S");
    var d = updateTick.Where(_ => Input.GetKeyDown(KeyCode.D)).Select(_ => "D");
    var a = updateTick.Where(_ => Input.GetKeyDown(KeyCode.A)).Select(_ => "A");

    // Display the pressed key
    w.Subscribe(Debug.Log).AddTo(this);
    s.Subscribe(Debug.Log).AddTo(this);
    d.Subscribe(Debug.Log).AddTo(this);
    a.Subscribe(Debug.Log).AddTo(this);

    // Stream of all previous keys combined
    var keyStream = Observable.Merge(w, a, s, d);

    // Stream of events when sequence is found
    var keyCodeFound = keyStream.Where(key => key == keyCode.First())
        .SelectMany(key => keyStream.StartWith(key).Buffer(keyCode.Length).First())
        .Where(list => list.SequenceEqual(keyCode));

    // Whenever the sequence is found, a message is displayed on the screen
    keyCodeFound.Subscribe(CombiHappened(), Debug.LogException)
        .AddTo(this);
}

private static System.Action<IList<string>> CombiHappened() => items => {
    Debug.LogWarning("Key combination happened! " + string.Join(",", items.ToArray()));
};

First case: where we start with the sequence directly: Starting with sequence

Second case: where we start with randoms letter first then the sequence Starting with random letters first

I am very happy. Now I should try to look for any sequence that I specify. Hopefully it goes smoothly!

Mehdi Saffar
  • 691
  • 1
  • 8
  • 24