質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

91.27%

  • Unity

    2458questions

    Unityは、ユニティテクノロジーが開発したゲームエンジンです。 主にモバイルやブラウザ向けのゲーム製作に利用されていましたが、3Dの重力付きゲームが簡単に作成できることから需要が増え、現在はマルチプラットフォームに対応しています。 言語はC言語/C++で書かれていますが、C#、JavaScript、Booで書かれたコードにも対応しています。

  • RxJS

    8questions

  • RxSwift

    6questions

  • RxJava

    4questions

  • Rx.NET

    1questions

[Rx] 効率の良いストリームの合成方法

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 93

IShikawan

score 1059

ご質問させていただきます。よろしくお願いいたします。
Unity用のRxプラグインを使用させていただき開発しております。その中でストリームの合成がすごく難しくコードが複雑になってしまいこれで正しいのか分からないのでご質問させていただきたいです。

環境

・Unity 2017.2.0p4
UniRx 5.5.0

実装したい処理

  1. (初期化) Vector2のデータをランダムに1つ生成
  2. (初期化) ランダムに1度00秒遅延させる
  3. (開始) UpdateAsObservableでループ開始
  4. (判定) 1で生成したデータとオブジェクトの距離が一定の距離になるまで処理を続ける
  5. (OnNext) Subscribeで1で生成したデータに徐々に近づける
  6. (Oncompleted) 完了後1から繰り返し

実現したいこと

コードが複雑になっているので簡潔な方法がありましたらご教授いただきたいです。プロパティまたはフィールド(クロージャ)などの外部からの参照無しで実装したいです。言語は問いません。大変お手数ですがよろしくお願いいたします。

現状のコード

static readonly float WALK_SPEED = .7f;
static readonly float RAND_INTERVAL_MIN = 4;
static readonly float RAND_INTERVAL_MAX = 10;
static readonly float DISTANCE = .05f;
static readonly Vector2 RAND_POS_LEFT_TOP = new Vector2(-1, 3);
static readonly Vector2 RAND_POS_RIGHT_BOTTOM = new Vector2(-2, 4);
Transform MyTransform;

void Start ()
{
  MyTransform = gameObject.transform;

  Observable.Empty<Vector2>().StartWith(() => GetRandPos())
    .SelectMany(pos => 
      Observable.Timer(TimeSpan.FromSeconds(UnityEngine.Random.Range(RAND_INTERVAL_MIN, RAND_INTERVAL_MAX)))
      .Select(_ => pos)
    )
    .SelectMany(pos => this.UpdateAsObservable().Select(_ => pos))
    .TakeWhile(pos => Vector2.Distance(pos, MyTransform.position) >= DISTANCE)
    .Select(pos => GetNextPos(pos, MyTransform.position))
    .RepeatUntilDestroy(this)
    .Subscribe(pos => MyTransform.position = pos);
}

Vector2 GetNextPos(Vector2 target,Vector2 me)
{
  var rad = Mathf.Atan2(target.y - me.y, target.x - me.x);

  me.x += WalkSpeed * Time.deltaTime * Mathf.Cos(rad);
  me.y += WalkSpeed * Time.deltaTime * Mathf.Sin(rad);
  return me;
}

Vector2 GetRandPos()
{
  var x = UnityEngine.Random.Range(
    RAND_POS_LEFT_TOP.x,
    RAND_POS_RIGHT_BOTTOM.x
  );

  var y = UnityEngine.Random.Range(
    RAND_POS_LEFT_TOP.y,
    RAND_POS_RIGHT_BOTTOM.y
  );

  return new Vector2(x, y);
}
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

+1

遅延抜きで繰り返し1回部分のストリーム構築を考えてみます。

求めたい座標のストリーム(一回分)をys_
元となる座標のストリームをxsとします。

ys_xsの最新値とys_の最後の値から構築されるのでScan()オペレータを使います
ys_はある条件で終了するのでTakeWhile()オペレータを使います

ys_ = xs.Scan(...).TakeWhile(...)

説明のためys_の初期値y0を与えてys_を構築する関数をfと置きます。
f = y0 => ys_

次に遅延部分を考えます。

Observable.Timer()を使い一つだけ値をランダムな遅延させて流します。
流れてきた値は捨てて、y0を生成して差し替えます。
これにSelectMany(f)をくっつければ遅延してys_を生成して流してくれるはず

Observable.Timer(...).Select(...).SelectMany(f)

後は全体をRepeat()すれば繰り返すはず

ys = Observable.Timer(...).Select(...).SelectMany(f).RepeatUntilDestroy(...)

UniRxではなくRxで、Vector2でなく1次元でとりあえずやってみましたが
あまり簡潔になってないですね。すみません。

// テスト用にもととなる座標ストリームをでっち上げる
var xs = Observable.Interval(TimeSpan.FromSeconds(1))
    .Select(x => (double)x)
    .Take(60).Publish().RefCount();


var ys = Observable.Timer(TimeSpan.FromMilliseconds(rand.Next(1500, 4500)))
    .Select(_ => GetRandPos())
    .SelectMany(y0 =>
        xs.Scan(new { Y = y0, Dist = 10.0 }, // 初っ端でTakeWhileに引っかからないように(雑)
            (acc, x) =>
            {
                var y = GetNextPos(x, acc.Y);
                return new { Y = y, Dist = Math.Abs(x - y) };
            })
        .TakeWhile(acc => acc.Dist > 1.0)
        .Select(acc => acc.Y))
    .Repeat();


xs.Timestamp().Subscribe(x => Console.WriteLine($"x , {x.Timestamp.Ticks} , {x.Value}"));
ys.Timestamp().Subscribe(y => Console.WriteLine($"y , {y.Timestamp.Ticks} , {y.Value}"));

適当にGetHOGE()を設定して動かしてみました:
イメージ説明
横軸:時間
縦軸:座標


回答ではなく質問です。追記依頼覧が狭いので。
(聞いたところで難しそうなので回答できるかはちょっと...すみません)

  • ある移動体Pの座標(ストリーム)から、ある規則で動く別の移動体Qの座標(ストリーム)を求めたい。
  • Qの座標はPの座標とQの前回座標から求められる。(GetNextPos())
  • Qの初期座標はランダムに与えられる。(GetRandPos())
  • PとQの距離が一定以下になったら、Qの座標はランダムに与え直される。

ということでよろしいでしょうか?

ランダムな遅延が読み解けなかったんですが、
目的のストリームはどちらですか?

1,2,...,5 座標の出力タイミング
R リセット

1)

|-------(Delay)------ 1 --- 2 --- 3 --- 4 --- R -----(Delay)---- 5 --->

2)

|1 ------(Delay)------ 2 --- 3 --- 4 --- R5 ---(Delay)--->

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/12/27 11:41

    説明不足ですみません。ご返答ありがとうございます。箇条書きの内容に間違いありません。図にしていただいているものは1)が目的のストリームとなります。

    キャンセル

  • 2017/12/28 19:21

    具体的にご教授いただきありがとうございます。
    Scanを使用したことがないので、いただいたコードを元に勉強させていただます。Timestampはこういう風に使うんですね。大変勉強になりました。Rx凄く難しいですが便利なので理解を更に深めていきたいと思います。

    キャンセル

  • 2018/01/05 13:48

    今更ですが気になっていたので

    ラムダ式のアンダースコアは「捨てられる引数」など、
    どうでもいい値に使うという暗黙の了解があります(私だけ?)
    なもので、
    .TakeWhile(_ => Vector2.Distance(_, MyTransform.position) >= DISTANCE)
    のように、ラムダ式の本体側に_が出てくるとびっくりします。

    キャンセル

  • 2018/01/05 21:06

    確かにそうですね。ご指摘ありがとうございます。コードも修正しました。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

ただいまの回答率

91.27%

関連した質問

  • 受付中

    Unityでのナビゲーションラインの実装について

    unityでナビゲーションラインを実装したいと思っています。 画像中央の青いラインみたいなもの、プレイヤーから特定のオブジェクトに対して線を引き、方向を教える。 現在LineRe

  • 解決済

    Unityで3DObjectを一点からランダムのスピードで生成させ続けたい。

    Unityで3DのObjectを一点からランダムのスピードで生成させ続けたいのですが、 空のGameObjectsにプレハブ化したものを割り当てる風なことはわかりました。 しか

  • 解決済

    [Unity]落とすアイテムを増やしたい

    Unityでりんごが落ちてくるゲームを作っています アイテムを2つまでは落とせるようになったのですが3つ目のアイテムをどうすれば落とせるのかがわかりません どうすれば3つ目4つ

  • 受付中

    画面のタッチした場所にオブジェクトを徐々に移動させる(iTween)

    前提・実現したいこと タッチしたところにオブジェクトを徐々に移動させたい 発生している問題・エラーメッセージ タッチしたところに瞬間移動する 該当のソースコード

  • 解決済

    unityでランゲームを作っています

    前提・実現したいこと 今、unityで簡単なランゲームを作っています。 前から数パターンの壁を例えば何秒ごとにそのパターンの中からランダムで迫ってくる ようなことをしたいのですがど

  • 解決済

    Unityで回転制限をつけたいです。

    前提・実現したいこと 下記コードでAキー、Dキーを押している間それぞれY軸の-方向、Y軸の+方向に回転するよう書きましたが、90度回転したら回転できないようにする機能をつけたいです

  • 解決済

    Texture2Dをななめに切り出す方法

    Texture2Dをななめに切り出す方法を探しています。 4点を基準に画像を切り取る関数などはないでしょうか? GetPixelsの引数も一つの座標と幅、高さしかないためどう斜めに

  • 解決済

    HPがちゃんと満タンにならない【(int)Mathf.Lerp】の加算問題

    前提・実現したいこと 下記のコードの動作を維持しながら、表示がちゃんと表示されるようになって欲しい。 発生している問題・エラーメッセージ キャラクターがダメージを受けてHPが減

同じタグがついた質問を見る

  • Unity

    2458questions

    Unityは、ユニティテクノロジーが開発したゲームエンジンです。 主にモバイルやブラウザ向けのゲーム製作に利用されていましたが、3Dの重力付きゲームが簡単に作成できることから需要が増え、現在はマルチプラットフォームに対応しています。 言語はC言語/C++で書かれていますが、C#、JavaScript、Booで書かれたコードにも対応しています。

  • RxJS

    8questions

  • RxSwift

    6questions

  • RxJava

    4questions

  • Rx.NET

    1questions