【UniRx】第二季:操作符大全

前提:本人用的Unity2019.3.0f3,从AssetStore上直接下的UniRx 7.1.0;(摘自凉鞋)

【第三章节】

一、Linq 与 UniRx 操作符

UniRx 有非常多的操作符,比如(Where,First)等等。
这些操作符的意思与 LINQ 的操作符基本一致。
在讲解 UniRx 操作符之前,要先简单介绍一下 LINQ 的操作符。
因为学习 LINQ 操作符可以加深我们对 UniRx 的理解。

LINQ 简介
LINQ 是在 C# 3 发布的一个概念。主要是一种查询语法的实现,它可以像 SQL 语句一样查询 C# 的数据列表、XML 文件 和数据库。
最简单的就是 List 的 ForEach。
ForEach 是 LINQ 的链式写法。
其功能和我们常用的 foreach 是一样的。
代码如下:

var testList = new List<int> { 1, 2, 3 };
        testList.ForEach(num => Debug.Log(num));

输出结果为:
1
2
3
非常简单。
那么我们之前在 UniRx 的操作符也是通用的。

var firstNumber = testList.First(); 
Debug.Log(firstNumber);
testList.Where(num => num > 1)
.ForEach(number => Debug.Log(number));

输出结果为:
1
2
3
Where 就是过滤的意思。

为什么先介绍 LINQ?

这个答案,在前边已经讲过了。
就是“学习 LINQ 可以加深我们对 UniRx 的理解”。
而且,有的操作符比较晦涩,使用 LINQ 可以排除 UniRx 异步的一个干扰。只要把事件源当做一个
List 就好理解了。
接下来我们简单回顾一下 Rx 的历史。

Rx 的历史

ReactiveX 是 Reactive Extensions 的缩写,一般简写为 Rx,最初是 LINQ 的一个扩展,由微软的架构师Erik Meijer 领导的团队开发,在 2012 年 11 月开源,Rx 是一个编程模型,目标是提供一致的编程接口,帮助开发者更方便的处理异步数据流,Rx 库支持 .NET、JavaScript 和 C++,Rx 近几年越来越流行了,现在已经支持几乎全部的流行编程语言了,Rx 的大部分语言库由 ReactiveX 这个组织负责维护,比较流行的有 RxJava/RxJS/Rx.NET,而 Unity 的版本,就是 UniRx。

什么是 Rx

微软给的定义是,Rx 是一个函数库,让开发者可以利用可观察序列和 LINQ 风格查询操作符来编写异步和基于事件的程序,使用 Rx,开发者可以用 Observables 表示异步数据流,用 LINQ 操作符查询异步数据流, 用 Schedulers 参数化异步数据流的并发处理,Rx 可以这样定义:Rx = Observables + LINQ + Schedulers。
ReactiveX.io(官⽅) 给的定义是,Rx 是一个使⽤可观察数据流进行异步编程的编程接⼝,ReactiveX结合了观察者模式、迭代器模式和函数式编程的精华。
这里所说的观察者模式、迭代器模式、函数式编程等会在第三章原理部分进行介绍。
那么以上就算是对 Rx 的一个严谨的定义了。
那么接下来,在介绍每个操作符之前呢,都会先通过 LINQ 进行介绍。这样做是因为 LINQ 相⽐
UniRx 更好理解,UniRx 因为是在时间上大部分是异步的,所以会对同学们造成⼲扰。
本质上 LINQ 和 UniRx 是非常相似的,这个具体会在后边介绍。

Rx 的应用

像比较知名的 Microsoft、Netflix、Github、Trello、SoundCloud 都在用 Rx 的各个语⾔实现版本。
而 Unity 的 uFrame 、QFramework 等框架都集成了 UniRx。还有一些框架如 BindingRx、EcsRx 等也是非常优秀的框架,只不过不太出名。但是也非常值得去研究。

二、之前所学的 UniRx 操作符总结

 

 从本章开始,我们遇到的所有操作符,都按照一定的格式进行介绍。
格式暂定如下:
1. LINQ 定义
2. 示例代码
3. 查询表达式 (如果有)
4. UniRx 图解
5. 示例代码
6. 查询表达式 (如果有)
7. 所属分类及 UniRx 知识树位置
这样大家好进行一个知识的梳理,对知识的掌握更加清晰。

三、Where 操作符

Where 操作符,在第一章第二章都有进行接触。这节课主要是实践一下上两堂课所说的格式规范。
也就是介绍一个操作符的时候,先介绍 LINQ 这种方式。

LINQ Where 简介:
LINQ 中的 Where 操作符与 SQL 命令中的 Where 作用相似,都是起到范围限定也就是过滤作用的,而判断条件就是它后面所接的子句。

LINQ Where 示例代码:
查询大于 45 岁的学生:

using System.Collections.Generic;
using System.Linq;
using UnityEngine;

public class LINQWhereExample : MonoBehaviour
{
    class Student
    {
        public string Name;
        public int Age;
    }
    void Start()
    {
        var students = new List<Student>()
        {
            new Student(){ Name="张三",Age=50},
            new Student(){ Name="张三",Age=50},
            new Student(){ Name="李四",Age=40},
        };

        var oldStudents = students.Where(student => student.Age > 45);
        foreach(var student in oldStudents)
        {
            Debug.Log(student.Name);
        }
    }
}

输出结果为:
张三
张三
在这里呢,要介绍一个概念,叫做查询语句/句法。

LINQ Where 查询式

以上的代码,改成查询句式后,代码如下:

using System.Collections.Generic;
using System.Linq;
using UnityEngine;

public class LINQWhereQueryExample : MonoBehaviour
{
    class Student 
    {
        public string Name;
        public int Age;
    }
    void Start()
    {
        var students = new List<Student>()
        {
            new Student(){ Name="张三",Age=50},
            new Student(){ Name="张三",Age=50},
            new Student(){ Name="李四",Age=40},
        };
        var oldStudents = from oldStudent in students
                          where oldStudent.Age > 45
                          select oldStudent;
        foreach (var student in oldStudents)
        {
            Debug.Log(student.Name);
        }
    }
}

代码非常容易理解。
输出的结果,与前边一样:
张三
张三

UniRx Where 示例代码

鼠标点击事件监听:

using UnityEngine;
using UniRx;

public class UniRxWhereExamle : MonoBehaviour
{
    void Start()
    {
        Observable.EveryUpdate()
            .Where(_ => Input.GetMouseButtonDown(0))
            .Subscribe(_ => { Debug.Log("鼠标按下"); })
            .AddTo(this);
    }
}

UniRx Where 查询式:
改成查询式如下

using UnityEngine;
using UniRx;

public class UnIRxWhereQueryExample : MonoBehaviour
{
    void Start()
    {
        var mouseClickEventStreams = from updateEvent in Observable.EveryUpdate()
                                     where Input.GetMouseButtonDown(0)
                                     select updateEvent;
        mouseClickEventStreams.Subscribe(_ => { Debug.Log("鼠标按下"); })
            .AddTo(this);
    }
}

结果和上述代码一样。

四、Select 操作符

在上一堂课,介绍 Where 操作符的时候,不管是 UniRx 还是 LINQ,其查询表达式都多出了一个
select。
var oldStudents = from oldStudent in students
where oldStudent.Age > 45
select oldStudent;
但是在链式的写法中,却没有使用其对应的 Select 操作符,这是为什么呢?
var oldStudents = students.Where(student => student.Age > 45);
原因是,在 查询式写法中,编译器要求必须要在最后 select一下。
而链式写法,我们可以理解成省略掉了。
不省略的版本如下:
var oldStudents = students
.Where(student => student.Age > 45)
.Select(student => student);
其实就是选择了一下自⼰。
除了选择自己,其实还可以选择一个其他类型的变量作为结果。
比如:

var oldStudentNames = students
.Where(student => student.Age > 45)
.Select(student => student.Name);
既然提到了 Select 我们今天就正式学习一下 Select 操作符。

LINQ Select 简介

LINQ 中的 Select 操作符 与 SQL 命令中的 Select 作用相似,但是位置不同,查询表达式中的 select 及所接子句是放在最后并把子句中的变量也就是结果返回来。
LINQ Select 示例代码

using System.Collections.Generic;
using UnityEngine;
using UniRx;
using System.Linq;

public class LINQSelectExample : MonoBehaviour
{
    class Student
    {
        public string Name;
        public int Age;
    }
    void Start()
    {
        var students = new List<Student>()
        {
            new Student(){ Name="张三",Age=50},
            new Student(){ Name="张三",Age=50},
            new Student(){ Name="李四",Age=40},
        };
        var oldStudentNames = students.Where(student => student.Age > 45)
            .Select(student => student.Name);
        foreach(var studentName in oldStudentNames)
        {
            Debug.Log(studentName);
        }
    }
}

输出结果为:
张三
张三

LINQ Select 查询式

using System.Collections.Generic;
using UnityEngine;
using UniRx;
using System.Linq;

public class LINQSelectQueryExample : MonoBehaviour
{
    class Student
    {
        public string Name;
        public int Age;
    }
    void Start()
    {
        var students = new List<Student>()
        {
            new Student(){ Name="张三",Age=50},
            new Student(){ Name="张三",Age=50},
            new Student(){ Name="李四",Age=40},
        };
        var oldStudentNames = from student in students
                              where student.Age > 45
                              select student.Name;
        foreach(var studentName in oldStudentNames)
        {
            Debug.Log(studentName);
        }
    }
}

UniRx Select 示例代码

using UnityEngine;
using UniRx;

public class UniRxSelectExamle : MonoBehaviour
{
    void Start()
    {
        Observable.EveryUpdate()
            .Where(_ => Input.GetMouseButtonDown(0))
            .Select(_=> "鼠标按下")
            .Subscribe(Debug.Log)
            .AddTo(this);
    }
}

UniRx Select 查询式代码

using UnityEngine;
using UniRx;

public class UnIRxSelectQueryExample : MonoBehaviour
{
    void Start()
    {
        var mouseClickEventStreams = from updateEvent in Observable.EveryUpdate()
                                     where Input.GetMouseButtonDown(0)
                                     select "鼠标按下";
        mouseClickEventStreams.Subscribe(Debug.Log)
            .AddTo(this);
    }
}

五、First 操作符

LINQ First 简介
取序列中的第一个元素。First 有两种形式,一种是直接获取第一个元素,第二种则是取序列中满足条件的第一个元素。
LINQ First 代码示例:
第一种形式

using System.Collections.Generic;
using System.Linq;
using UnityEngine;

public class LINQFirstExample : MonoBehaviour
{
    class Student
    {
        public string Name;
        public int Age;
    }
    void Start()
    {
        var students = new List<Student>()
        {
            new Student(){ Name="张三",Age=50},
            new Student(){ Name="张三",Age=50},
            new Student(){ Name="李四",Age=40},
        };

        var oldStudents = students.Where(student => student.Age > 45)
            .First();
        Debug.Log(oldStudents.Name);
    }
}


第二种形式代码示例:

using System.Collections.Generic;
using UnityEngine;
using UniRx;
using System.Linq;

public class LINQFirstConditionExample : MonoBehaviour
{
    class Student
    {
        public string Name;
        public int Age;
    }
    void Start()
    {
        var students = new List<Student>()
        {
            new Student(){ Name="张三",Age=50},
            new Student(){ Name="张三",Age=50},
            new Student(){ Name="李四",Age=40},
        };
        var oldStudent = students
            .First(student => student.Age > 45);
        Debug.Log(oldStudent.Name);
    }
}

LINQ First 查询式

using System.Collections.Generic;
using UnityEngine;
using UniRx;
using System.Linq;

public class LINQFirstQueryExample : MonoBehaviour
{
    class Student
    {
        public string Name;
        public int Age;
    }
    void Start()
    {
        var students = new List<Student>()
        {
            new Student(){ Name="张三",Age=50},
            new Student(){ Name="张三",Age=50},
            new Student(){ Name="李四",Age=40},
        };
        var oldStudent = (from student in students select student)
            .First(student => student.Age > 45);
        Debug.Log(oldStudent.Name);
    }
}

UniRx First 示例代码

using UnityEngine;
using UniRx;

public class UniRxFirstExample : MonoBehaviour
{
    void Start()
    {
        Observable.EveryUpdate()
            .First(_ => Input.GetMouseButtonDown(0))
            .Subscribe(_ => { Debug.Log("鼠标按下"); })
            .AddTo(this);
    }
}

六、Distinct 操作符

LINQ Distinct 简介:
筛选序列中不相同的值。用于查询不重复的结果集。生成 SQL 语句为: SELECT DISTINCT [City] FROM[Customers]

LINQ Distinct 示例代码

using System.Collections.Generic;
using UnityEngine;
using UniRx;
using System.Linq;

public class LINQDistinctExample : MonoBehaviour
{
    void Start()
    {
        var names = new List<string>()
        {
            "张三",
            "张三",
            "李四",
        };

        var distinctNames = names.Distinct();
        foreach(var distinctName in distinctNames)
        {
            Debug.Log(distinctName);
        }
    }
}

输出结果为:
张三
李四

对象的过滤,也是支持的,只不过有点麻烦,需要实现一个类来实现。
LINQ Distinct 查询式示例代码

using System.Collections.Generic;
using UnityEngine;
using UniRx;
using System.Linq;

public class LINQDistinctQueryExample : MonoBehaviour
{
    void Start()
    {
        var names = new List<string>()
        {
            "张三",
            "张三",
            "李四",
        };

        var distinctNames = (from name in names select name).Distinct();
        foreach(var distinctName in distinctNames)
        {
            Debug.Log(distinctName);
        }
    }
}

其实,查询式,还是有点啰嗦。不如直接用链式表达式。

UniRx Distinct 示例代码

using System.Collections.Generic;
using UnityEngine;
using UniRx;
using System.Linq;

public class UniRxDistinctExample : MonoBehaviour
{
    class Student
    {
        public string Name;
        public int Age;
    }

    void Start()
    {
        var students = new List<Student>()
        {
            new Student(){ Name="张三",Age=50},
            new Student(){ Name="张三",Age=50},
            new Student(){ Name="李四",Age=40},
        };

        students.ToObservable()
            .Distinct(student => student.Name)
            .Subscribe(student =>
            {
                Debug.Log(student.Name);
            });
    }
}

UniRx 的 Distinct 在操作 List 时候,除了支持 LINQ 所支持的两种方式外,还支持传入一个特定条件函数。

using UnityEngine;
using UniRx;
using System.Linq;

public class UniRxDistinctExample2 : MonoBehaviour
{
    void Start()
    {
        var leftClickStream = Observable.EveryUpdate()
            .Where(_ => Input.GetMouseButtonDown(0))
            .Select(_ => "left clicked");
        var rightClickStream = Observable.EveryUpdate()
            .Where(_ => Input.GetMouseButtonDown(1))
            .Select(_ => "right clicked");
        Observable.Merge(leftClickStream, rightClickStream)
            .Distinct()
            .Subscribe(Debug.Log)
            .AddTo(this);
    }
}

以上代码结果为,不管点击多少次鼠标左键,还是鼠标右键,输出只输出一次 “left clicked” 和“right clicked”。

七、Last

LINQ Last 简介
取序列中的最后一个元素

LINQ Last 代码示例:

using System.Collections.Generic;
using System.Linq;
using UnityEngine;

public class LINQLastExample : MonoBehaviour
{
    class Student
    {
        public string Name;
        public int Age;
    }
    void Start()
    {
        var students = new List<Student>()
        {
            new Student() {Name = "张三", Age = 50},
            new Student() {Name = "张三", Age = 50},
            new Student() {Name = "李四", Age = 40}
        };
        var lastStudent = students.Last();
        Debug.Log(lastStudent.Name);
    }
}

输出结果为
李四

Last 属于分页操作符。
UniRx Last 代码示例

using System.Collections.Generic;
using UniRx;
using UnityEngine;
public class UniRxLastExample : MonoBehaviour
{
    class Student
    {
        public string Name;
        public int Age;
    }
    private void Start()
    {
        new List<Student>()
        {
            new Student() {Name = "张三", Age = 50},
            new Student() {Name = "张三", Age = 45},
            new Student() {Name = "李四", Age = 50}
        }
        .ToObservable()
        .Last(student => student.Age == 50)
        .Subscribe(student => { Debug.Log(student.Name); });
    }
}

Last 支持传⼊一个条件函数,而 LINQ 中的 LAST 只能传⼊一个条件方法。

八、SelectMany

LINQ SelectMany 简介
微软官方的定义是: 将序列的每个元素投影到 IEnumerable<T> 并将结果序列合并为一个序列。
对每项再进行遍历处理再进行合成序列。
LINQ SelectMany 示例代码

using System.Collections.Generic;
using System.Linq;
using UnityEngine;
public class LINQSelectManyExample : MonoBehaviour
{
    class Student
    {
        public string Name;
        public int Age;
    }
    void Start()
    {
        var students = new List<Student>()
        {
            new Student() {Name = "张三", Age = 50},
            new Student() {Name = "张三", Age = 45},
            new Student() {Name = "李四", Age = 50}
        };
        var singleChars = students.SelectMany(student => student.Name + ":" + student.Age);
        foreach (var singleChar in singleChars)
        {
            Debug.Log(singleChar);
        }
    }
}

输出结果为:
张三:50
张三:45
李四:50

UniRx SelectMany 代码示例
SelectMany 理解起来会比较晦涩,一般在 UniRx 中,主要是完成 Coroutine 的顺序执行功能。
代码如下:

using System.Collections;
using UniRx;
using UnityEngine;
public class UniRxSelectManyExample : MonoBehaviour
{
    IEnumerator A()
    {
        yield return new WaitForSeconds(1.0f);
        Debug.Log("A");
    }
    IEnumerator B()
    {
        yield return new WaitForSeconds(2.0f);
        Debug.Log("B");
    }
    IEnumerator C()
    {
        yield return new WaitForSeconds(3.0f);
        Debug.Log("C");
    }
    void Start()
    {
        var streamA = Observable.FromCoroutine(A);
        var streamB = Observable.FromCoroutine(B);
        var streamC = Observable.FromCoroutine(C);
        streamA.SelectMany(streamB.SelectMany(streamC))
            .Subscribe(_ => Debug.Log("Hello"));
    }
}

输出结果为:
A
B
C
Hello

九、Take

LINQ Take 简介
从序列的开头返回指定数量的相邻元素。
LINQ Take 代码示例

using System.Linq;
using UnityEngine;
public class LINQTakeExample : MonoBehaviour
{
    void Start()
    {
        int[] grades = { 59, 82, 70, 56, 92, 98, 85 };
        var topThreeGrades = grades.OrderByDescending(grade => grade) // 根据分数降序排序
            .Take(3);
        Debug.Log("分数前三名的是:");
        foreach (var topThreeGrade in topThreeGrades)
        {
            Debug.Log(topThreeGrade);
        }
    }
}

输出结果为:
分数前三名的是:
98
92
85

UniRx Take 示例代码

using UniRx;
using UniRx.Triggers;
using UnityEngine;
namespace UniRxLesson
{
    public class UniRxTakeExample : MonoBehaviour
    {
        void Start()
        {
            this.UpdateAsObservable()
                .Where(_ => Input.GetMouseButtonDown(0))
                .Take(5)
                .Subscribe(_ => Debug.Log(1));
        }
    }
}

输出结果为,只有前 5 次鼠标点击才会输出 1。

十、Concat

LINQ Concat 简介
连接两个序列。
LINQ Concat 示例代码

using System.Linq;
using UnityEngine;
public class LINQConcatExample : MonoBehaviour
{
    private class Pet
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }
    void Start()
    {
        Pet[] cats =
        {
            new Pet {Name = "Barley", Age = 8},
            new Pet {Name = "Boots", Age = 4},
            new Pet {Name = "Whiskers", Age = 1}
        };
        Pet[] dogs =
        {
            new Pet {Name = "Bounder", Age = 3},
            new Pet {Name = "Snoopy", Age = 14},
            new Pet {Name = "Fido", Age = 9}
        };
        var petNames = cats.Select(cat => cat.Name)
            .Concat(dogs.Select(dog => dog.Name));
        foreach (var petName in petNames)
        {
            Debug.Log(petName);
        }
    }
}

输出结果:
Barley
Boots
Whiskers
Bounder
Snoopy
Fido

Concat 操作符连接多个 Observable 的输出,就好像它们是一个 Observable,第一个 Observable发射的所有数据在第二个 Observable 发射的任何数据前面,以此类推。
直到前面一个Observable终止,Concat 才会订阅额外的一个 Observable。注意:因此,如果你尝试连接一个"热" Observable(这种 Observable 在创建后立即开始发射数据,即使没有订阅者),Concat 将不会看到也不会发射它之前发射的任何数据。

Concat 与 Merge 和 WhenAll 类似,是联合操作符。
UniRx Concat 示例代码

using UniRx;
using UniRx.Triggers;
using UnityEngine;
public class UniRxConcatExample : MonoBehaviour
{
    void Start()
    {
        var a = this.UpdateAsObservable().Take(3).Select(_ => "A");
        var b = this.UpdateAsObservable().Take(2).Select(_ => "B");
        var c = a.Concat(b);
        c.Subscribe(Debug.Log);
    }
}

输出结果:
A
A
A
B
B

十一、WhenAll

LINQ All 简介
确定序列中的所有元素是否都满足条件。
LINQ All 示例代码

using System.Linq;
using UnityEngine;
public class LINQAllExample : MonoBehaviour
{
    private class Pet
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }
    void Start()
    {
        Pet[] pets =
        {
            new Pet {Name = "Barley", Age = 10},
            new Pet {Name = "Boots", Age = 4},
            new Pet {Name = "Whiskers", Age = 6}
        };
        var allStartWithB = pets.All(pet =>pet.Name.StartsWith("B"));
        Debug.LogFormat("{0} pet names start with 'B'.",allStartWithB ? "All" : "Not all");
    }
}

输出结果为:
Not all pet names start with 'B'.

传递一个谓词函数给 WhenAll 操作符,这个函数接受原始 Observable 发射的数据,根据计算返回一个布尔值。WhenAll 返回一个只发射一个单个布尔值的 Observable,如果原始 Observable 正常终止并且每一项数据都满足条件,就返回 true;如果原始 Observable 的任意一项数据不满⾜足条件就返回False。

UniRx WhenAll 示例代码

using System.Collections;
using UniRx;
using UnityEngine;

public class UniRxWhenAllExample : MonoBehaviour
{
    IEnumerator A()
    {
        yield return new WaitForSeconds(1.0f);
        Debug.Log("A");
    }
    IEnumerator B()
    {
        yield return new WaitForSeconds(1.0f);
        Debug.Log("B");
    }
    IEnumerator C()
    {
        yield return new WaitForSeconds(1.0f);
        Debug.Log("C");
    }
    private void Start()
    {
        var streamA = Observable.FromCoroutine(A);
        var streamB = Observable.FromCoroutine(B);
        var streamC = Observable.FromCoroutine(C);
        Observable.WhenAll(streamA, streamB, streamC)
        .Subscribe(_ => { Debug.Log("Completed"); });
    }
}

输出结果为(A、B、C 输出顺序不一定):
B
C
A
Completed

十二、OfType

LINQ OfType 简介:
根据指定类型筛选 IEnumerable 的元素。
LINQ OfType 代码示例:

using System.Collections;
using System.Linq;
using UnityEngine;

public class LINQOfTypeExample : MonoBehaviour
{
    void Start()
    {
        var list = new ArrayList { 30, 30.0f, "test" };
        var filterList = list.OfType<float>();
        foreach (var obj in filterList)
        {
            Debug.Log(obj);
        }
    }
}

输出结果为:
30

UniRx OfType 示例代码

using UniRx;
using UnityEngine;
public class UniRxOfTypeExample : MonoBehaviour
{
    void Start()
    {
        // 创建一个 Subject(Observable)
        var objects = new Subject<object>();
        // 订阅该 Observable,进行类型过滤
        objects.OfType<object, string>().Subscribe(Debug.Log);
        //手动发送数据
        objects.OnNext(1);
        objects.OnNext(2);
        objects.OnNext("3");
        objects.OnNext(4);
        //手动结束
        objects.OnCompleted();
    }
}

输出结果为:
3

十三、Cast

LINQ Cast 简介
将 IEnumerable 的元素强制转换为指定的类型。
LINQ Cast 代码示例

using System.Collections;
using System.Linq;
using UnityEngine;
public class LINQCastExample : MonoBehaviour
{
    void Start()
    {
        var fruits = new ArrayList { "mango", "apple", "lemon" };
        var fruitNames =fruits.Cast<string>();
        // 等同于
        // var fruitNames = fruits.Select(fruit => fruit.ToString);
        foreach (var fruit in fruitNames)
        {
            Debug.Log(fruit);
        }
    }
}

输出代码为:
mango
apple
lemon

UniRx Cast 代码示例

using UniRx;
using UnityEngine;

public class UniRxCastExample : MonoBehaviour
{
    void Start()
    {
        // 创建一个 Subject(Observable)
        var objects = new Subject<object>();
        // 订阅该 Observable,进行类型转换
        objects.Cast<object, int>().Subscribe(i => Debug.Log(i));
        // 手动发送数据
        objects.OnNext(1);
        objects.OnNext(2);
        objects.OnNext(3);
        // 手动结束
        objects.OnCompleted();
    }
}

输出结果为:
1
2
3

十四、GroupBy

LINQ GroupBy 简介
对序列中的元素进行分组。
LINQ GroupBy 代码示例

using System.Collections.Generic;
using System.Linq;
using UnityEngine;
public class LINQGroupByExample : MonoBehaviour
{
    class Student
    {
        public string Name;
        public int Age;
    }
    void Start()
    {
        var students = new List<Student>()
        {
            new Student {Name = "张三", Age = 50},
            new Student {Name = "张三", Age = 50},
            new Student {Name = "李四", Age = 40}
        };
        var studentGroup4Names = students.GroupBy(student => student.Name);
        foreach (var studentGroup in studentGroup4Names)
        {
            Debug.LogFormat("Group Key:{0}", studentGroup.Key);
            foreach (var student in studentGroup)
            {
                Debug.LogFormat("Name:{0} Age:{1}", student.Name, student.Age);
            }
        }
    }
}

输出结果为:
Group Key:张三
Name:张三 Age:50
Name:张三 Age:50
Group Key:李四
Name:李四 Age:40

UniRx GroupBy 代码示例

using System;
using UniRx;
using UnityEngine;
public class UniRxGroupByExample : MonoBehaviour
{
    void Start()
    {
        Observable.Interval(TimeSpan.FromSeconds(0.1))
            .Take(10)
            .GroupBy(i => i % 3)
            .Subscribe(group =>
            {
                group.Subscribe(number =>
                {
                    Debug.LogFormat("Key:{0},Number:{1}", group.Key, number);
                });
            });
    }
}

输出结果为:
Key:0,Number:0
Key:1,Number:1
Key:2,Number:2
Key:0,Number:3
Key:1,Number:4
Key:2,Number:5
Key:0,Number:6
Key:1,Number:7
Key:2,Number:8
Key:0,Number:9

十五、Range

LINQ Range 简介
生成指定范围内的整数的序列。
LINQ Range 代码示例

using System.Linq;
using UnityEngine;
public class LINQRangeExample : MonoBehaviour
{
    void Start()
    {
        var squares = Enumerable.Range(5, 3).Select(x => x * x);
        foreach (var square in squares)
        {
            Debug.Log(square);
        }
    }
}

输出结果:
25
36
49

UnIRx Range 代码示例

using UniRx;
using UnityEngine;
public class UniRxRangeExample : MonoBehaviour
{
    void Start()
    {
        var squares = Observable.Range(5, 3).Select(x => x * x);
        squares.Subscribe(square => { Debug.Log(square); });
    }
}

输出结果为
25
36
48
通过 LINQ 和 UniRx 的比较可以看到,LINQ 也有类似 Observable 的 Enumerable。而Subscribe 其实相当于 LINQ 中的 foreach。

十六、Skip

LINQ Skip 简介
跳过序列中指定数量的元素,然后返回剩余的元素。
LINQ Skip 代码示例

using System.Linq;
using UnityEngine;
public class LINQSkipExample : MonoBehaviour
{
    void Start()
    {
        int[] grades = { 59, 82, 70, 56, 92, 98, 85 };
        var lowerGrades =
            grades.OrderByDescending(g => g).Skip(3);
        foreach (var grade in lowerGrades)
        {
            Debug.Log(grade);
        }
    }
}

输出结果为
82
70
59
56

UniRx Skip 代码示例

using UniRx;
using UniRx.Triggers;
using UnityEngine;
public class UniRxSkipExample : MonoBehaviour
{
    void Start()
    {
        this.UpdateAsObservable()
            .Where(_ => Input.GetMouseButtonDown(0))
            .Skip(5).Subscribe(_ => { Debug.Log("mouse clicked"); });
    }
}

输出结果为,鼠标点击第六次时,才开始输出”mouse clicked”。

十七、TakeWhile

LINQ TakeWhile 简介
如果指定的条件为 true,则返回序列中的元素,然后跳过剩余的元素。
LINQ TakeWhile 示例代码

using System.Linq;
using UnityEngine;
public class LINQTakeWhileExample : MonoBehaviour
{
    void Start()
    {
        var fruits = new[]
        {
             "apple", "banana", "mango", "orange","passionfruit", "grape"
        };
        var fruitsAfterOrange = fruits.TakeWhile(fruit => fruit != "orange");
        foreach (var fruit in fruitsAfterOrange)
        {
            Debug.Log(fruit);
        }
    }
}

输出结果为
apple
banana
mango

UniRx TakeWhile 代码示例

using UniRx;
using UniRx.Triggers;
using UnityEngine;
public class UniRxTakeWhileExample : MonoBehaviour
{
    void Start()
    {
        this.UpdateAsObservable()
            .TakeWhile(l => !Input.GetMouseButton(0))
            .Subscribe(_ => Debug.Log("before mouse clicked"));
    }
}

运行结果为,持续输出”before mouse clicked”,当鼠标点击之后不再输出 “before mouse clicked”

十八、SkipWhile

LINQ SkipWhile 简介
如果指定的条件为 true,则跳过序列中的元素,然后返回剩余的元素。
LINQ SkipWhile 代码示例

using System.Linq;
using UnityEngine;
public class LINQSkipWhileExample : MonoBehaviour
{
    void Start()
    {
        int[] grades = { 59, 82, 70, 56, 92, 98, 85 };
        var lowerGrades =
            grades.OrderByDescending(grade => grade)
            .SkipWhile(grade => grade >= 80);
        foreach (var grade in lowerGrades)
        {
            Debug.Log(grade);
        }
    }
}

输出结果为:
70
59
56

UniRx SkipWhile 代码示例

using UniRx;
using UniRx.Triggers;
using UnityEngine;
public class UniRxSkipWhileExample : MonoBehaviour
{
    void Start()
    {
        this.UpdateAsObservable()
            .SkipWhile(_ => !Input.GetMouseButton(0))
            .Subscribe(_ => { Debug.Log("mouse button down"); });
    }
}

实现的逻辑为,当点击鼠标后,持续输出 mouse button down

十九、Zip(.Net4)

Zip 是在 .Net4 支持的操作符。
LINQ Zip 简介
将指定函数应用于两个序列的对应元素,以生成结果序列。
LINQ Zip 示例

using System.Linq;
using UnityEngine;
public class LINQZipExample : MonoBehaviour
{
    void Start()
    {
        int[] numbers = { 1, 2, 3, 4 };
        string[] words = { "one", "two", "three" };
        var numbersAndWords = numbers.Zip(words, (first, second) => first + " " + second);
        foreach (var item in numbersAndWords)
        {
            Debug.Log(item);
        }
    }
}

输出结果为:
1 one
2 two
3 three

UniRx Zip 示例代码

using UniRx;
using UniRx.Triggers;
using UnityEngine;
public class ZipExample : MonoBehaviour
{
    void Start()
    {
        var rightStream = this.UpdateAsObservable()
            .Where(_ =>Input.GetMouseButtonDown(0));
        var leftStream = this.UpdateAsObservable()
            .Where(_ =>Input.GetMouseButtonDown(1));
        leftStream.Zip(rightStream, (l, r) => Unit.Default)
            .Subscribe(_ => { Debug.Log("ok"); });
    }
}

运行之后,点击鼠标的顺序为, 左->右->左->右->左->左->左->右->右->右
输出的结果为:
// 左
ok // 右
// 左
ok // 右
// 左
// 左
// 左
ok // 右
ok // 右
ok // 右

二十、Repeat

LINQ Repeat 简介
在生成序列中重复该值的次数。
LINQ Repeat 代码示例

using System.Linq;
using UnityEngine;
public class LINQRepeatExample : MonoBehaviour
{
    void Start()
    {
        var strings = Enumerable.Repeat("I like programming.", 5);
        foreach (var str in strings)
        {
            Debug.Log(str);
        }
    }
}

输出结果为:
I Like programming.
I Like programming.
I Like programming.
I Like programming.
I Like programming.

UniRx Repeat 代码示例

using UniRx;
using UniRx.Triggers;
using UnityEngine;
public class UniRxRepeatExample : MonoBehaviour
{
    void Start()
    {
        var leftClickStream = this.UpdateAsObservable()
            .Where(_ =>Input.GetMouseButtonDown(0));
        var rightClickStream = this.UpdateAsObservable()
            .Where(_ =>Input.GetMouseButtonDown(1));
        leftClickStream.Zip(rightClickStream, (e1, e2) => "Clicked")
            .First().Repeat().Subscribe(Debug.Log);
    }
}

运行之后,点击鼠标的顺序为, 左->右->左->右->左->左->左->右->右->右
输出的结果为:
// 左
Clicked // 右
// 左
Clicked // 右
// 左
// 左
// 左
Clicked // 右
// 右
// 右

二十一、TakeLast

LINQ TakeLast 简介
获取序列的最后⼏项
LINQ TakeLast 代码示例
由于 TakeLast 操作符是在 .Net Core 提供的,所以就不进行代码示例的介绍了。

UniRx TakeLast 代码示例

using System.Linq;
using UniRx;
using UnityEngine;
public class UniRxTakeLastExample : MonoBehaviour
{
    void Start()
    {
        int[] grades = { 59, 82, 70, 56, 92, 98, 85 };
        var bottomThreeGrades = grades.OrderByDescending(grade => grade)
            .ToObservable().TakeLast(3);
        bottomThreeGrades.Subscribe(buttomThreeGrade => Debug.Log(buttomThreeGrade));
    }
}

输出结果为
70
59
56

二十二、Single /Default

LINQ Single 简介
返回序列中的单个特定元素,与 First 非常类似,但是 Single 要确保其满足条件的元素在序列中只有一个。
LINQ Single 代码示例

using System.Linq;
using UnityEngine;

public class LINQSingleExample : MonoBehaviour
{
    void Start()
    {
        string[] fruits = { "apple", "banana", "mango","orange", "passionfruit", "grape" };
        var fruit1 = fruits.Single(fruit => fruit.Length > 10);
        Debug.Log(fruit1);
    }
}

输出结果为
passionfruit

Single 操作符也与 First 类似,但是如果原始 Observable 在完成之前不是正好发射一次数据,它会抛出一个 NoSuchElementException。

UniRx Single 代码示例

using UniRx;
using UnityEngine;
public class UniRxSingleExample : MonoBehaviour
{
    void Start()
    {
        string[] fruits = { "apple", "banana", "mango","orange", "passionfruit", "grape" };
        fruits.ToObservable().Single(fruit => fruit.Length > 10)
            .Subscribe(Debug.Log);
    }
}

输出结果为
passionfruit

二十三、ToArray

LINQ ToArray 简介
从 IEnumerable<T> 中创建数组。
LINQ ToArray 示例代码

using System.Collections.Generic;
using System.Linq;
using UnityEngine;
public class LINQToArrayExample : MonoBehaviour
{
    private class Package
    {
        public string Company { get; set; }
        public double Weight { get; set; }
    }
    void Start()
    {
        var packages =
        new List<Package>
        { 
            new Package {Company = "Coho Vineyard", Weight = 25.2},
            new Package {Company = "Lucerne Publishing", Weight = 18.7},
            new Package {Company = "Wingtip Toys", Weight = 6.0},
            new Package {Company = "Adventure Works", Weight = 33.8}
        };
        var companies = packages.Select(pkg => pkg.Company).ToArray();
        foreach (var company in companies)
        {
            Debug.Log(company);
        }
    }
}

输出结果:
Coho Vineyard
Lucerne Publishing
Wingtip Toys
Adventure Works

UniRx ToArray 示例代码

using UniRx;
using UnityEngine;
public class UniRxToArrayExample : MonoBehaviour
{
    void Start()
    {
        var subject = new Subject<int>();
        subject.ToArray().Subscribe(intArray =>
        {
            Debug.Log(intArray.GetType().ToString());
            foreach (var i in intArray)
            {
                Debug.Log(i);
            }
        });
        subject.OnNext(1);
        subject.OnNext(2);
        subject.OnNext(3);
        subject.OnCompleted();
    }
}

输出结果为:
System.Int32[]
1
2
3

二十四、ToList

LINQ ToList 简介
从 IEnumerable<T> 创建一个 List<T>。
LINQ ToList 示例代码

using System.Linq;
using UnityEngine;
public class LINQToListExample : MonoBehaviour
{
    void Start()
    {
        string[] fruits = { "apple", "passionfruit", "banana", "mango","orange", "blueberry", "grape", "strawberry" };
        var lengths = fruits.Select(fruit => fruit.Length).ToList();
        Debug.Log(lengths.GetType());
        foreach (var length in lengths)
        {
            Debug.Log(length);
        }
    }
}

输出结果:
System.Collections.Generic.List`1[System.Int32]
5
12
6
5
6
9
5
10

UniRx ToList 示例代码

using UniRx;
using UnityEngine;
public class UniRxToListExample : MonoBehaviour
{
    void Start()
    {
        var subject = new Subject<int>();
        subject.ToList().Subscribe(intList =>
        {
            Debug.Log(intList.GetType().ToString());
            foreach (var i in intList)
            {
                Debug.Log(i);
            }
        });
        subject.OnNext(1);
        subject.OnNext(2);
        subject.OnNext(3);
        subject.OnCompleted();
    }
}

输出结果为
System.Collections.Generic.List`1[System.Int32]
1
2
3

二十五、Aggregate

LINQ Aggregate 简介
对序列应用累加器函数。 将指定的种子值用作累加器的初始值,并使用指定的函数选择结果值。
LINQ Aggregate 代码示例

using System.Linq;
using UnityEngine;
public class LINQAggregateExample : MonoBehaviour
{
    void Start()
    {
        var numbers = new[] { 1, 2, 3, 4, 5 };
        var result = numbers.Aggregate((total, next) => total * next);
        Debug.LogFormat("5的阶乘为:{0}", result); //返回120,也就是1*2*3*4*5
    }
}

输出结果为:
5的阶乘为:120

UniRx Aggregate 代码示例

using UniRx;
using UnityEngine;
public class UniRxAggregateExample : MonoBehaviour
{
    private void Start()
    {
        Observable.Range(0, 8)
            .Aggregate(0, (acc, currentValue) => acc + 5)
            .Subscribe(xx =>{Debug.Log(xx);});
    }
}

输出结果为:
40

二十六、Empty

LINQ Empty 简介
返回具有指定类型参数的空 IEnumerable<T>。
LINQ Empty 示例代码

using System.Collections.Generic;
using System.Linq;
using UnityEngine;
public class LINQEmptyExample : MonoBehaviour
{
    void Start()
    {
        string[] names1 = { "Hartono, Tommy" };
        string[] names2 = { "Adams, Terry", "Andersen, Henriette Thaulow","Hedlund, Magnus", "Ito, Shu" };
        string[] names3 = { "Solanki, Ajay", "Hoeing, Helge","Andersen, Henriette Thaulow","Potra, Cristina", "Iallo, Lucio" };
        var namesList = new List<string[]> { names1, names2, names3 };
        var allNames = namesList.Aggregate(Enumerable.Empty<string>(),(current, next) => next.Length > 3 ? current.Union(next) :current);
        foreach (var name in allNames)
        {
            Debug.Log(name);
        }
    }
}

输出结果为:
Adams, Terry
Andersen, Henriette Thaulow
Hedlund, Magnus
Ito, Shu
Solanki, Ajay
Hoeing, Helge
Potra, Cristina
Iallo, Lucio

UniRx Empty 代码示例

using UniRx;
using UnityEngine;
public class UniRxEmptyExample : MonoBehaviour
{
    void Start()
    {
        var s = Observable.Empty<Unit>();
        s.Subscribe(e => Debug.Log("e: " + e), () => Debug.Log("OnCompleted"));
    }
}

输出结果为
OnCompleted

二十七、小结

到本堂课为止呢,我们学习完了 UniRx 从 LINQ 所借鉴来的所有操作符了,这部分操作符的性价⽐其实是最高的,因为理解了这些操作符,LINQ 的大部分操作符也就都理解了。
而且在本章的前几节有提过,通过 LINQ 操作符去理解 UniRx 操作符会更加容易,但是呢 UniRx
的 LINQ 操作符数量有限,LINQ 操作符也只占UniRx 操作符的 50% 左右。
不过没关系,我们学习完本章之后,理解下一章的操作符会更加容易,在下一章节我们学习,UniRx从 Rx.Net 中借鉴的操作符。

【第四章节】

一、简介

 二、Interval

Interval 示意图

 Interval 代码示例

using System;
using UniRx;
using UnityEngine;
public class IntervalExample : MonoBehaviour
{
    void Start()
    {
        Observable.Interval(TimeSpan.FromSeconds(1)).Subscribe(seconds =>
        {
            Debug.LogFormat("当前时间:{0} s", seconds);
        }).AddTo(this);
    }
}

输出结果为:

当前时间:0s

当前时间:1s

当前时间:2s

.........

三、TakeUntil

TakeUntil 示意图
当第二个 Observable 发射了一项数据或者终止时,丢弃原始Observable发射的任何数据

TakeUntil 订阅并开始发射原始Observable,它还监视你提供的第二个 Observable。如果第二个
Observable 发射了一项数据或者发射了一个终止通知,TakeUntil 返回的Observable会停止发射原始Observable并终止。 

TakeUntil 示例代码

using UniRx;
using UniRx.Triggers;
using UnityEngine;
public class TakeUntilExample : MonoBehaviour
{
    void Start()
    {
        this.UpdateAsObservable()
            .TakeUntil(Observable.EveryUpdate()
            .Where(_ => Input.GetMouseButtonDown(0)))
            .Subscribe(_ =>
            {
                Debug.Log(123);
            });
    }
}

 输出结果为,运行之后持续输出 123,当点击鼠标左键后,停止输出 123。

 四、SkipUntil

SkipUntil 示意图
丢弃原始 Observable 发射的数据,直到第二个 Observable 发射了一项数据

 SkipUntil 代码示例

using UniRx;
using UniRx.Triggers;
using UnityEngine;
public class SkipUntilExample : MonoBehaviour
{
    void Start()
    {
        // 条件
        var clickStream = this.UpdateAsObservable().
            Where(_ =>Input.GetMouseButtonDown(0));
        // 监听
        this.UpdateAsObservable()
            .SkipUntil(clickStream)
            .Subscribe(_ => Debug.Log("鼠标按过了"));
    }
}

输出结果为,点击鼠标左键之后就开始持续输出 “鼠标按过了”

五、Buffer(缓冲)

Buffer 示意图

 

Buffer 代码示例

using System;
using UniRx;
using UnityEngine;
public class BufferExample : MonoBehaviour
{
    void Start()
    {
        Observable.Interval(TimeSpan.FromSeconds(1.0f))
            .Buffer(TimeSpan.FromSeconds(3.0f))
            .Subscribe(_ =>{Debug.LogFormat("currentTime:{0}",DateTime.Now.Second);})
            .AddTo(this);
    }
}

输出结果为:
11
14
17
...

六、Throttle(节流阀)

Throttle 示意图

仅在过了一段指定的时间还没发射数据时才发射一个数据 

Throttle 代码示例

using System;
using UniRx;
using UnityEngine;
public class ThrottleExample : MonoBehaviour
{
    void Start()
    {
        Observable.EveryUpdate()
            .Where(_ => Input.GetMouseButtonDown(0))
            .Throttle(TimeSpan.FromSeconds(1))
            .Subscribe(_ => Debug.Log("一秒过后"));
    }
}

输出结果为,点击鼠标后 1 秒内不再点击则输出,如果有点击则重新计时 1 秒再输出。

七、Delay

Delay 示意图
延迟一段指定的时间再发射来自Observable的发射物

Delay 代码示例

using System;
using UniRx;
using UnityEngine;
public class DelayExample : MonoBehaviour
{
    void Start()
    {
        Observable.EveryUpdate()
            .Where(_ => Input.GetMouseButtonDown(0))
            .Delay(TimeSpan.FromSeconds(1.0f))
            .Subscribe(_ => { Debug.Log("mouse clicked"); })
            .AddTo(this);
    }
}

输出的结果为,点击鼠标 1 秒之后输出 mouse clicked,每次点击事件都是 1 秒之后才输出。

八、Return

Return 示意图

 Return 示例代码

using UniRx;
using UnityEngine;
public class ReturnExample : MonoBehaviour
{
    void Start()
    {
        // 就执⾏一次,类似 set
        Observable.Return("hello")
            .Subscribe(Debug.Log);
    }
}

输出结果为
hello

九、Timer

Timer 示意图
创建一个Observable,它在一个给定的延迟后发射一个特殊的值。

Timer 代码示例

using System;
using UniRx;
using UnityEngine;
public class TimerExample : MonoBehaviour
{
    void Start()
    {
        Observable.Timer(TimeSpan.FromSeconds(5.0f))
            .Subscribe(_ => { Debug.Log("after 5 seconds"); })
            .AddTo(this);
    }
}

执行结果为,在 5 秒会后输出 “after 5 seconds”

十、Sample

Sample 示意图
定期发射 Observable 最近发射的数据项

Sample 操作符定时查看一个Observable,然后发射自上次采样以来它最近发射的数据。
在某些实现中,有一个ThrottleFirst操作符的功能类似,但不是发射采样期间的最近的数据,而
是发射在那段时间内的第一项数据。
RxJava将这个操作符实现为sample和throttleLast。
注意:如果自上次采样以来,原始Observable没有发射任何数据,这个操作返回的Observable在那段时间内也不会发射任何数据。 

 sample(别名throttleLast)的一个变体按照你参数中指定的时间间隔定时采样(TimeUnit指定时间
单位)。
sample的这个变体默认在computation调度器上执行,但是你可以使用第三个参数指定其它的调
度器。

sample的这个变体每当第二个Observable发射一个数据(或者当它终止)时就对原始Observable进行采样。第二个Observable通过参数传递给sample。
sample的这个变体默认不在任何特定的调度器上执行。 

Sample 示例代码

using System;
using UniRx;
using UnityEngine;
public class SampleExample : MonoBehaviour
{
    void Start()
    {
        Observable.Interval(TimeSpan.FromMilliseconds(50))
            .Sample(TimeSpan.FromSeconds(1))
            .Subscribe(_ => { Debug.Log(DateTime.Now.Second); })
            .AddTo(this);
    }
}

输出结果为
52
53
54
...

十一、Timestamp

Timestamp 示意图
给 Observable 发射的数据项附加一个时间戳

 RxJava 中的实现为 timestamp,它将一个发射 T 类型数据的 Observable 转换为一个发射类型为
Timestamped<T>的数据的 Observable,每一项都包含数据的原始发射时间。
timestamp 默认在 immediate 调度器上执行,但是可以通过参数指定其它的调度器。

Timestamp 代码示例

using System;
using UniRx;
using UnityEngine;
public class Timestamp : MonoBehaviour
{
    void Start()
    {
        Observable.Interval(TimeSpan.FromSeconds(1.0f))
            .Timestamp()
            .Subscribe(timestamp => { Debug.Log(timestamp); })
            .AddTo(this);
    }
}

输出结果为:

0@2021/10/21 6:31:20 +00:00

1@2021/10/21 6:31:21 +00:00

2@2021/10/21 6:31:22 +00:00

3@2021/10/21 6:31:23 +00:00

.............

十二、ThrottleFirst

ThrottleFirst 示意图

throttleFirst 与 throttleLast/sample 不同,在每个采样周期内,它总是发射原始 Observable
的第一项数据,而不是最近的一项。
throttleFirst 操作符默认在 computation 调度器上执行,但是你可以使用第三个参数指定其
它的调度器。
它与 Sample非常类似。

ThrottleFirst 代码示例

using System;
using UniRx;
using UniRx.Triggers;
using UnityEngine;
public class ThrottleFirstExample : MonoBehaviour
{
    void Start()
    {
        this.UpdateAsObservable()
            .Where(_ => Input.GetMouseButtonDown(0))
            .ThrottleFirst(TimeSpan.FromSeconds(5))
            .Subscribe(x => Debug.Log("Clicked!"));
    }
}

运行结果为
鼠标点击之后,立即输出 “Clicked”,输出之后的 5 秒内点击无效。

十三、TimeInterval

TimeInterval 示意图
将一个发射数据的 Observable 转换为发射那些数据发射时间间隔的 Observable

TimeInterval 操作符拦截原始 Observable 发射的数据项,替换为发射表示相邻发射物时间间隔的对象。 

TimeInterval 代码示例

using System;
using UniRx;
using UnityEngine;
public class TimeIntervalExample : MonoBehaviour
{
     void Start()
    {
        Observable.Interval(TimeSpan.FromMilliseconds(750))
            .TimeInterval()
            .Subscribe(timeInterval => Debug.LogFormat("{0}: {1}",timeInterval.Value, timeInterval.Interval));
    }
}

输出结果为:

0: 00:00:00.7428084

1: 00:00:00.7522600

2: 00:00:00.7519011

3: 00:00:00.7528451

..........

十四、Defer

Defer 示意图
直到有观察者订阅时才创建 Observable,并且为每个观察者创建一个新的 Observable

Defer 操作符会一直等待直到有观察者订阅它,然后它使用 Observable 工厂方法生成一个
Observable。它对每个观察者都这样做,因此尽管每个订阅者都以为自己订阅的是同一个
Observable,事实上每个订阅者获取的是它们自己的单独的数据序列。
在某些情况下,等待直到最后一分钟(就是知道订阅发生时)才生成 Observable 可以确保
Observable 包含最新的数据。

Defer 代码示例

using System;
using UniRx;
using UnityEngine;
public class DeferExample : MonoBehaviour
{
    void Start()
    {
        var random = new System.Random();
        Observable.Defer(() => Observable.Start(() => random.Next()))
            .Delay(TimeSpan.FromMilliseconds(1000))
            .Repeat()
            .Subscribe(randomNumber => Debug.Log(randomNumber));
    }
}

输出结果为
1790623781
67128575
913269989
2027467965
297262093
1000073077

十五、Never

Never 示意图
创建一个不发射数据也不终止的Observable

 Never 示例代码

using UniRx;
using UnityEngine;
public class NeverExample : MonoBehaviour
{
    void Start()
    {
        var never = Observable.Never<string>();
        //similar to a subject without notifications
        var subject = new Subject<string>();
    }
}

目前还不知道干嘛用,没有输出结果。

十六、Scan

Scan 示意图
连续地对数据序列的每一项应用一个函数,然后连续发射结果

 Scan操作符对原始Observable发射的第一项数据应用一个函数,然后将那个函数的结果作为自己的第一项数据发射。它将函数的结果同第二项数据一起填充给这个函数来产生它自己的第二项数据。它持续进行这个过程来产生剩余的数据序列。这个操作符在某些情况下被叫做accumulator。

与 Aggregate 类似,但是 Scan 是每次进行输出,而 Aggregate 则是结束计算后进行输出。

Scan 代码示例

using UniRx;
using UnityEngine;
public class ScanExample : MonoBehaviour
{
    void Start()
    {
        Observable.Range(0, 8)
            .Scan(0, (acc, currentValue) => acc + 5)
            .Subscribe(xx =>{Debug.Log(xx);});
    }
}

输出结果为
5
10
15
20
25
30
35
40

十七、Switch

Switch 示意图
将一个发射多个 Observables 的 Observable 转换成另一个单独的 Observable,后者发射那些
Observables 最近发射的数据项

Switch 订阅一个发射多个 Observables 的 Observable。它每次观察那些 Observables 中的一个,Switch 返回的这个 Observable 取消订阅前一个发射数据的 Observable,开始发射最近的
Observable 发射的数据。注意:当原始 Observable 发射了一个新的 Observable 时(不是这个新的Observable 发射了一条数据时),它将取消订阅之前的那个 Observable。这意味着,在后来那个Observable 产生之后到它开始发射数据之前的这段时间里,前一个 Observable 发射的数据将被丢弃(就像图例上的那个⻩色圆圈一样)。 

Switch 代码示例

using UniRx;
using UnityEngine;
public class SwitchExample : MonoBehaviour
{
    void Start()
    {
        var buttonDownStream = Observable.EveryUpdate()
            .Where(_ => Input.GetMouseButtonDown(0));
        var buttonStream = Observable.EveryUpdate()
            .Where(_ =>Input.GetMouseButtonUp(0));
        buttonDownStream.Select(_ =>
        {
            Debug.Log("mouse button down");
            return buttonStream;
        })
            .Switch()
            .Subscribe(_ => { Debug.Log("mouse button up"); });
    }
}

执行结果为,当按下鼠标时输出 “mouse button down” 抬起之后输出 “mouse button up”。

十八、StartWith

StartWith 示意图

如果你想要一个 Observable 在发射数据之前先发射一个指定的数据序列,可以使用StartWith 操作符。(如果你想⼀个Observable发射的数据末尾追加一个数据序列可以使用 Concat 操作符。)

可接受一个Iterable或者多个Observable作为函数的参数。

StartWith 代码示例

using UniRx;
using UnityEngine;
public class StartWithExample : MonoBehaviour
{
    void Start()
    {
        Observable.Return("sikiedu.com")
            .StartWith("http://")
            .Aggregate((current, next) => current + next)
            .Subscribe(Debug.Log);
    }
}

输出结果为
http://sikiedu.com

十九、CombineLatest

CombineLatest 示意图
当两个 Observables 中的任何一个发射了数据时,使用一个函数结合每个 Observable 发射的最近数据项,并且基于这个函数的结果发射数据。

CombineLatest 操作符行为类似于 zip,但是只有当原始的 Observable 中的每一个都发射了一条数据时 zip 才发射数据。CombineLatest 则在原始的 Observable 中任意一个发射了数据时发射⼀一条数据。当原始 Observables 的任何一个发射了一条数据时,CombineLatest 使用一个函数结合它们最近发射的数据,然后发射这个函数的返回值。 

 CombineLatest 示例代码

using UniRx;
using UniRx.Triggers;
using UnityEngine;
public class CombineLatestExample : MonoBehaviour
{
    void Start()
    {
        var a = 0;
        var i = 0;
        var leftStream = this.UpdateAsObservable()
            .Where(_ =>Input.GetMouseButtonDown(0))
            .Select(_ => (++a).ToString());
        var rightStream = this.UpdateAsObservable()
            .Where(_ =>Input.GetMouseButtonDown(1))
            .Select(_ => (++i).ToString());
        leftStream.CombineLatest(rightStream, (left, right) => left + right)
            .Subscribe(Debug.Log);
    }
}

执行结果为,点击鼠标顺序:左 -> 右 -> 左 -> 右 -> 左 -> 左 -> 右 -> 右
则输出如下
// 左
11 // 右
21 // 左
22 // 右
32 // 左
42 // 左
43 // 右
44 // 右

二十、Do

Do 示意图
注册一个动作作为原始 Observable 生命周期事件的一种占位符

Do 代码示例

using System;
using UniRx;
using UnityEngine;
public class DoExample : MonoBehaviour
{
    void Start()
    {
        Observable.ReturnUnit()
            .Delay(TimeSpan.FromSeconds(1.0f)) 
            .Do(_ => { Debug.Log("after 1 seconds"); })
            .Delay(TimeSpan.FromSeconds(1.0f))
            .Do(_ => { Debug.Log("after 2 seconds"); })
            .Delay(TimeSpan.FromSeconds(1.0f))
            .Subscribe(_ => { Debug.Log("after 3 seconds"); });
    }
}

输出结果为
after 1 seconds // 第 1 秒结束
after 2 seconds // 第 2 秒结束
after 3 seconds // 第 3 秒结束

二十一、Merge

Merge 示意图

Merge 代码示例

using UniRx;
using UniRx.Triggers;
using UnityEngine;
public class MergeExample : MonoBehaviour
{
    void Start()
    {
        var aStream = this.UpdateAsObservable()
            .Where(_ =>Input.GetMouseButtonDown(0))
            .Select(_ => "A");
        var bStream = this.UpdateAsObservable()
            .Where(_ =>Input.GetMouseButtonDown(1))
            .Select(_ => "B");
        aStream.Merge(bStream).Subscribe(Debug.Log);
    }
}

输出结果为,点击鼠标左键则输出 “A”,点击鼠标右键则输出”B”。

二十二、Materialize/Dematerialize

Materialize 示意图
Materialize 将数据项和事件通知都当做数据项发射,

一个合法的有限的Obversable将调用它的观察者的 onNext 方法零次或多次,然后调用观察者的
onCompleted 或 onError 正好一次。Materialize 操作符将这一系列调用,包括原来的 onNext 通
知和终止通知 onCompleted 或 onError 都转换为一个Observable发射的数据序列。

Dematerialize 操作符是Materialize的逆向过程,它将Materialize转换的结果还原成它原本的
形式。
dematerialize反转这个过程,将原始Observable发射的Notification对象还原成Observable
的通知。

Materialize/Dematerialize 代码示例

using System;
using UniRx;
using UnityEngine;
public class MaterializeExample : MonoBehaviour
{
    void Start()
    {
        var subject = new Subject<int>();
        var onlyExceptions = subject.Materialize()
            .Where(n => n.Exception != null)
            .Dematerialize();
        subject.Subscribe(i => Debug.LogFormat("Subscriber 1: {0}", i),ex => Debug.LogFormat("Subscriber 1 exception: {0}", ex.Message));
        onlyExceptions.Subscribe(i => Debug.LogFormat("Subscriber 2: {0}", i),ex => Debug.LogFormat("Subscriber 2 exception: {0}", ex.Message));
        subject.OnNext(123);
        subject.OnError(new Exception("Test Exception"));
    }
}

输出结果为
Subscriber 1: 123
Subscriber 1 exception: Test Exception
Subscriber 2 exception: Test Exception

二十三、IgnoreElements

IgnoreElements 示意图
不发射任何数据,只发射Observable的终止通知

IgnoreElements操作符抑制原始Observable发射的所有数据,只允许它的终止通知(onError或
onCompleted)通过。
如果你不关⼼一个Observable发射的数据,但是希望在它完成时或遇到错误终止时收到通知,你可以对Observable使用ignoreElements操作符,它会确保永远不会调用观察者的onNext()方法。 

IgnoreElements 代码结果

using UniRx;
using UnityEngine;
public class IgnoreElementsExample : MonoBehaviour
{
    void Start()
    {
        var subject = new Subject<int>();
        //Could use subject.Where(_=>false);
        var noElements = subject.IgnoreElements();
        subject.Subscribe(i => Debug.LogFormat("subject.OnNext({0})", i),() => Debug.LogFormat("subject.OnCompleted()"));
        noElements.Subscribe(i => Debug.LogFormat("noElements.OnNext({0})", i),() => Debug.LogFormat("noElements.OnCompleted()"));
        subject.OnNext(1);
        subject.OnNext(2);
        subject.OnNext(3);
        subject.OnCompleted();
    }
}

输出结果为:
subject.OnNext(1)
subject.OnNext(2)
subject.OnNext(3)
subject.OnCompleted()
noElements.OnCompleted()

二十四、DistinctUntilChanged

DistinctUntilChanged 示意图

DistinctUntilChanged 

using UniRx;
using UnityEngine;
public class DistinctUntilChangedExample : MonoBehaviour
{
    void Start()
    {
        var subject = new Subject<int>();
        var distinct = subject.DistinctUntilChanged();
        subject.Subscribe(i => Debug.LogFormat("{0}", i),() => Debug.LogFormat("subject.OnCompleted()"));
        distinct.Subscribe(i => Debug.LogFormat("distinct.OnNext({0})", i),() => Debug.LogFormat("distinct.OnCompleted()"));
        subject.OnNext(1);
        subject.OnNext(2);
        subject.OnNext(3);
        subject.OnNext(1);
        subject.OnNext(1);
        subject.OnNext(4);
        subject.OnCompleted();
    }
}

输出结果为
1
distinct.OnNext(1)
2
distinct.OnNext(2)
3
distinct.OnNext(3)
1
distinct.OnNext(1)
1
4
distinct.OnNext(4)
subject.OnCompleted()
distinct.OnCompleted()

二十五、Create

Create 示意图
使⽤一个函数从头开始创建一个Observable

你可以使用 Create 操作符从头开始创建一个 Observable ,给这个操作符传递一个接受观察者作为参数的函数,编写这个函数让它的行为表现为一个 Observable 恰当的调用观察者的 onNext,onError和 onCompleted方法。

一个形式正确的有限Observable必须尝试调用观察者的onCompleted正好一次或者它的onError正好一次,而且此后不能再调用观察者的任何其它方法。

 Create 示例代码

using UniRx;
using UnityEngine;
public class CreateExample : MonoBehaviour
{
    void Start()
    {
        Observable.Create<int>(o =>
        {
            o.OnNext(1);
            o.OnNext(2);
            o.OnCompleted();
            return Disposable.Create(() => Debug.Log("观察者已取消订阅"));
        }).Subscribe(xx => { Debug.Log(xx); });
    }
}

输出结果
1
2
观察者已取消订阅

二十六、Amb

Amb 示意图
给定两个或多个 Observable,它只发射最先发射数据或通知的那个 Observable 的所有数据

传递多个Observable给 Amb 时,它只发射其中一个Observable的数据和通知:最先发送通知给 Amb的那个,不管发射的是一项数据还是一个 onError 或 onCompleted 通知。Amb 将忽略和丢弃其它所有Observables的发射物。

 

 Amb 代码示例

using System;
using UniRx;
using UnityEngine;
public class AmbExample : MonoBehaviour
{
    void Start()
    {
        Observable.Amb(
        Observable.Timer(TimeSpan.FromSeconds(3)).Select(_ => "3 sec"),
        Observable.Timer(TimeSpan.FromSeconds(10)).Select(_ => "10 sec"),
        Observable.Timer(TimeSpan.FromSeconds(2)).Select(_ => "2 sec"),
        Observable.Timer(TimeSpan.FromSeconds(22)).Select(_ => "30 sec"),
        Observable.Timer(TimeSpan.FromSeconds(6)).Select(_ => "5 sec"))
            .Subscribe(s => Debug.LogFormat("OnNext: {0}", s),() => Debug.Log("OnCompleted"));
    }
}

输出结果为
OnNext: 2 sec
OnCompleted

二十七、Timeout

Timeout 示意图
对原始 Observable 的一个镜像,如果过了一个指定的时长仍没有发射数据,它会发一个错误通知

如果原始Observable过了指定的一段时长没有发射任何数据,Timeout 操作符会以一个 onError 通知终止这个 Observable。

 Timeout 示例代码

using System;
using UniRx;
using UnityEngine;
public class UniRxTimeoutExample : MonoBehaviour
{
    void Start()
    {
        Observable.EveryUpdate()
            .Where(_ => Input.GetMouseButtonDown(1))
            .Take(10)
            .Timeout(TimeSpan.FromSeconds(1.0))
            .Subscribe(_ => Debug.Log("clicked"));
    }
}

运行结果为,当一秒内部做任何操作,则会报异常。

二十八、FromEvent

FromEvent 示意图
将其它种类的对象和数据类型转换为Observable

 代码示例

using System;
using UniRx;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
public class UniRxFromEventExample : MonoBehaviour
{
    event Action OnClickEvent;
    void Start()
    {
        transform.Find("Button").GetComponent<Button>().onClick.AddListener(() => OnClickEvent());
        Observable.FromEvent(action => OnClickEvent += action, action => OnClickEvent-= action)
            .Subscribe(_ => Debug.Log("button clicked"));
    }
}

输出结果为,当每次点击时,输出 button clicked

二十九、Publish

Publish 示意图
将普通的 Observable 转换为可连接的 Observable

可连接的 Observable (connectable Observable)与普通的 Observable 差不多,不过它并不会在被订阅时开始发射数据,而是直到使用了Connect操作符时才会开始。用这种方法,你可以在任何时候让一个Observable 开始发射数据。

 

有一个变体接受一个函数作为参数。这个函数用原始Observable发射的数据作为参数,产生一个新的数据作为ConnectableObservable给发射,替换原位置的数据项。实质是在签名的基础上添加一个Select操作。

Publish 代码示例

using UniRx;
using UnityEngine;
public class UniRxPublishExample : MonoBehaviour
{
    void Start()
    {
        var unshared = Observable.Range(1, 4);
        // Each subscription starts a new sequence
        unshared.Subscribe(i => Debug.Log("Unshared Subscription #1: " + i));
        unshared.Subscribe(i => Debug.Log("Unshared Subscription #2: " + i));
        // By using publish the subscriptions are shared, but the sequence doesn't start until Connect() is called.
        var shared = unshared.Publish();
        shared.Subscribe(i => Debug.Log("Shared Subscription #1: " + i));
        shared.Subscribe(i => Debug.Log("Shared Subscription #2: " + i));
        shared.Connect();
    }
}

输出结果为:
Unshared Subscription #1: 1
Unshared Subscription #1: 2
Unshared Subscription #1: 3
Unshared Subscription #1: 4
Unshared Subscription #2: 1
Unshared Subscription #2: 2
Unshared Subscription #2: 3
Unshared Subscription #2: 4
Shared Subscription #1: 1
Shared Subscription #2: 1
Shared Subscription #1: 2
Shared Subscription #2: 2
Shared Subscription #1: 3
Shared Subscription #2: 3
Shared Subscription #1: 4
Shared Subscription #2: 4

三十、RefCount

RefCount 示意图
让一个可连接的 Observable 行为像普通的Observable

可连接的 Observable (connectable Observable) 与普通的 Observable 差不多,不过它并不会在被订阅时开始发射数据,而是直到使用了Connect操作符时才会开始。用这种方法,你可以在任何时候让一个Observable开始发射数据。
RefCount操作符把从一个可连接的 Observable 连接和断开的过程自动化了。它操作一个可连接的Observable,返回一个普通的 Observable。当第一个订阅者订阅这个Observable时,RefCount 连接到下层的可连接 Observable。RefCount 跟踪有多少个观察者订阅它,直到最后⼀一个观察者完成才断开与下层可连接Observable的连接。

 RefCount 示例代码

using System;
using UniRx;
using UnityEngine;
public class RefCountExample : MonoBehaviour
{
    void Start()
    {
        var period = TimeSpan.FromSeconds(1);
        var observable = Observable.Interval(period).Do(l => Debug.LogFormat("Publishing{0}", l)) //side effect to show it is running
            .Publish()
            .RefCount();
        Observable.Timer(TimeSpan.FromSeconds(5.0f)).Subscribe(_ =>
        {
            Debug.Log("@@@@ subscribe @@@@");
                //observable.Connect(); Use RefCount instead now
                var subscription = observable.Subscribe(i => Debug.LogFormat("subscription : {0}", i));
            Observable.Timer(TimeSpan.FromSeconds(5.0f)).Subscribe(__ =>
            {
                Debug.Log("@@@@ unsubscribe @@@@");
                subscription.Dispose();
                observable.Subscribe(i => Debug.LogFormat("subscription 2: {0}", i));
            });
        });
    }
}

输出结果为:
// 5 秒后
@@@@ subscribe @@@@
Publishing 0
subscription : 0
Publishing 1
subscription : 1
10
Publishing 2
subscription : 2
Publishing 3
subscription : 3
@@@@ unsubscribe @@@@
Publishing 0
subscription 2: 0
Publishing 1
subscription 2: 1
Publishing 2
subscription 2: 2
Publishing 3
subscription 2: 3
...
除此之外,还有一个 Share 操作符:

/// <summary>
    /// same as Publish().RefCount()
    /// </summary>
    public static IObservable<T> Share<T>(this IObservable<T> source)
    {
        return source.Publish().RefCount();
    }

三十一、Replay

Replay 示意图
保证所有的观察者收到相同的数据序列,即使它们在Observable开始发射数据之后才订阅

可连接的 Observable (connectable Observable)与普通的 Observable 差不多,不过它并不会在被订阅时开始发射数据,而是直到使用了Connect操作符时才会开始。用这种方法,你可以在任何时候让一个Observable 开始发射数据。
如果在将一个Observable转换为可连接的Observable之前对它使用Replay操作符,产生的这个可
连接 Observable 将总是发射完整的数据序列给任何未来的观察者,即使那些观察者在这个Observable开始给其它观察者发射数据之后才订阅。

 

有一种 replay返回一个普通的Observable。它可以接受一个变换函数为参数,这个函数接受原始
Observable发射的数据项为参数,返回结果Observable要发射的一项数据。因此,这个操作符其实是replay变换之后的数据项。 

Replay 示例代码

using System;
using UniRx;
using UnityEngine;
public class UniRxReplayExample : MonoBehaviour
{
    void Start()
    {
        var period = TimeSpan.FromSeconds(1);
        var hot = Observable.Interval(period).Take(3).Publish();
        hot.Connect();
        Observable.Timer(period).Select(_ => hot.Replay()).Do(observable =>
        {
            observable.Connect();
            observable.Subscribe(i => Debug.LogFormat("first subscription: { 0}", i));
        })
            .Delay(period)
            .Do(observable => observable.Subscribe(i =>
            Debug.LogFormat("second subscription : {0}", i)))
            .Select(observable => Observable.EveryUpdate()
            .Where(_ => Input.GetMouseButtonDown(0))
            .Select(_ => observable)
            .First())
            .Switch()
            .Do(observable => observable.Subscribe(i =>
            Debug.LogFormat("third subscription : {0}", i)))
            .Subscribe();
    }
}

输出结果为
first subscription : 1
second subscription : 1
first subscription : 2
second subscription : 2
third subscription : 1
third subscription : 2

三十二、Connect

Connect 示意图
让一个可连接的Observable开始发射数据给订阅者

可连接的 Observable (connectable Observable) 与普通的 Observable 差不多,不过它并不会在被订阅时开始发射数据,而是直到使用了 Connect 操作符时才会开始。用这个方法,你可以等待所有的观察者都订阅了Observable 之后再开始发射数据。

 三十三、Throw

Throw 示意图

创建一个不发射数据以一个错误终止的 Observable

示例代码 

using System;
using UniRx;
using UnityEngine;
public class UniRxThrowExample : MonoBehaviour
{
    void Start()
    {
        Observable.Throw<string>(new Exception("error"))
            .Subscribe(_ => Debug.Log("不会输出"), e => Debug.LogFormat("发现异常:{ 0}", e.Message));
    }
}

输出结果:
发现异常:error

三十四、Catch

UniRx Catch 示意图
从onError通知中恢复发射数据

Catch操作符拦截原始Observable的onError通知,将它替换为其它的数据项或数据序列,让产⽣生的Observable能够正常终止或者根本不终止。
在某些ReactiveX的实现中,有一个叫onErrorResumeNext的操作符,它的行为与Catch相似。

UniRx Catch 示例代码

using System;
using UniRx;
using UnityEngine;

public class UniRxCatchExample : MonoBehaviour
{
    void Start()
    {
        Observable.Throw<string>(new Exception("error")).Catch<string, Exception>(e =>
        {
            Debug.LogFormat("catched exception:{0}", e.Message);
            return Observable.Timer(TimeSpan.FromSeconds(1.0f)).Select(_ => "timer called");
        })
            .Subscribe(result => Debug.Log(result));
    }
}

输出结果为:
catched exception:error
// 1 秒后
timer called

三十五、Finally

UniRx Finally 示意图
注册一个动作,当它产生的Observable终止之后会被调用,无论是正常还是异常终止。

 UniRx Finally 示例代码

using UniRx;
using UnityEngine;
public class UniRxFinallyExample : MonoBehaviour
{
    void Start()
    {
        var source = new Subject<int>();
        var result = source.Finally(() => Debug.Log("Finally action ran"));
        result.Subscribe(number => Debug.LogFormat("OnNext({0})", number), () => Debug.Log("OnCompleted()"));
        source.OnNext(1);
        source.OnNext(2);
        source.OnNext(3);
        //source.OnError(new Exception());
        source.OnCompleted();
    }
}

输出结果为
OnNext(1)
OnNext(2)
OnNext(3)
OnCompleted()
Finally action ran

三十六、DelaySubscription

UniRx DelaySubscription 示意图

UniRx DelaySubscription 代码示例

using System;
using UniRx;
using UnityEngine;
public class UniRxDelaySubscriptionExample : MonoBehaviour
{
    void Start()
    {
        Debug.Log(Time.time);
        Observable.ReturnUnit()
            .DelaySubscription(TimeSpan.FromSeconds(1.0f))
            .Subscribe(_ => Debug.Log(Time.time));
    }
}

输出结果为:
0
1.000366

三十七、PairWise

示例代码

using UniRx;
using UnityEngine;
public class UniRxPairWiseExample : MonoBehaviour
{
    void Start()
    {
        Observable.Range(0, 10)
            .Pairwise()
            .Subscribe(pair => Debug.Log(pair.Current + ":" + pair.Previous));
    }
}

输出结果为:
1:0
2:1
3:2
4:3
5:4
6:5
7:6
8:7
9:8

三十八、小结

我们在本章学习了UniRx 中原属于 Rx.Net 的部分操作符。这些操作符呢,大部分是非常实用的。少部分不太常用的部分呢,都放在比较靠后的位置去讲了。
很多操作符理解起来晦涩难懂,不过自己去动手练习一遍就大概明白了。
OK,本章的内容就到此为止了,我们在下一章中学习, UniRx 独有的操作符,这部分操作符,每个都非常实用,建议大家认真学习,一定会有收获的。

【第五章节】

一、简介

在上章的小结有提到过,这章的操作符是 UniRx 对 Unity 所支持的操作符。这些操作符都是 UniRx 独有的操作符。并且这些操作符非常实用,学习完这些,基本上 UniRx 可以全部替代 Unity 的 Coroutine了。

二、NextFrame

示例代码

using UniRx;
using UnityEngine;
public class UniRxNextFrameExample : MonoBehaviour
{
    void Start()
    {
        Debug.Log(Time.frameCount);
        Observable.NextFrame().Subscribe(_ => Debug.Log(Time.frameCount));
    }
}

输出结果为:
1
3

三、DelayFrame

DelayFrame 示例代码

using UniRx;
using UnityEngine;
public class UniRxDelayFrameExample : MonoBehaviour
{
    void Start()
    {
        Debug.Log(Time.frameCount);
        Observable.ReturnUnit()
            .DelayFrame(10)
            .Subscribe(_ => Debug.Log(Time.frameCount));
    }
}

输出结果为:
1
12

四、FrameInterval

using UniRx;
using UnityEngine;
public class UniRxFrameIntervalExample : MonoBehaviour
{
    void Start()
    {
        Observable.EveryUpdate()
            .Where(_ => Input.GetMouseButtonDown(0))
            .FrameInterval()
            .Subscribe(frameInterval => Debug.Log(frameInterval.Interval));
    }
}

运行结果为,会输出距离上一次鼠标点击所间隔的帧数。

五、BatchFrame

using UniRx;
using UnityEngine;
public class UniRxBatchFrameExample : MonoBehaviour
{
    void Start()
    {
        Observable.EveryUpdate()
            .Where(_ => Input.GetMouseButtonDown(0))
            .BatchFrame(100, FrameCountType.EndOfFrame)
            .Subscribe(clicks =>
            {
                Debug.Log(clicks.Count);
            });
    }
}

运行结果为,收集 每 100 帧内的点击事件,然后进行统一的输出。

六、ForEachAsync

using UniRx;
using UnityEngine;
public class UniRxForEachAsyncExample : MonoBehaviour
{
    void Start()
    {
        Observable.Range(0, 10)
            .ForEachAsync(number => Debug.Log(number))
            .Subscribe();
    }
}

输出结果为
0
1
2
3
4
5
6
7
8
9

七、FrameTimeInterval

using UniRx;
using UnityEngine;
public class UniRxFrameTimeInterval : MonoBehaviour
{
    void Start()
    {
        Observable.EveryUpdate()
            .Where(_ => Input.GetMouseButtonDown(0))
            .FrameTimeInterval()
            .Subscribe(frameTimeInterval =>Debug.Log(frameTimeInterval.Interval));
    }
}

运行结果为,当点击鼠标时,返回距离上一次点击的,帧总时间。

八、SampleFrame

using UniRx;
using UnityEngine;
public class UniRxSampleFrameExample : MonoBehaviour
{
    void Start()
    {
        Observable.EveryUpdate()
            .SampleFrame(5)
            .Subscribe(_ => Debug.Log(Time.frameCount));
    }
}

输出结果为
7
12
17
22
27

......

九、RepeatUntilDestroy

using System;
using UniRx;
using UnityEngine;
public class UniRxRepeatUntilExample : MonoBehaviour
{
    void Start()
    {
        Observable.Timer(TimeSpan.FromSeconds(1.0f))
            .RepeatUntilDestroy(this)
            .Subscribe(_ => Debug.Log("ticked"));
    }
}

运行结果为,每个一秒输出一次 ticked,当把脚本所在 GameObject 删除掉,则不再输出。

十、ObserveOnMainThread

using System;
using System.Threading;
using UniRx;
using UnityEngine;
public class UniRxSubscribeOnMainThreadExample : MonoBehaviour
{
    void Start()
    {
        Debug.Log(Time.time);
        Observable.Start(() =>
        {
            Thread.Sleep(TimeSpan.FromSeconds(1.0f));
            return 1;
        })
            .ObserveOnMainThread()
            .Subscribe(threadResult => Debug.LogFormat("{0} {1}",threadResult, Time.time));
    }
}

输出结果为:
0
// 2 秒后
1 1.034706

十一、DelayFrameSubscript

using System;
using UniRx;
using UnityEngine;
public class UniRxDelayFrameSubscription : MonoBehaviour
{
    void Start()
    {
        Debug.Log(Time.time);
        Observable.Timer(TimeSpan.FromSeconds(1.0f))
            .DelayFrameSubscription(TimeSpan.FromSeconds(1.0f))
            .Subscribe(_ => Debug.Log(Time.time));
    }
}

输出结果为:
0
// 2 秒后
2.014145

十二、ThrottleFirstFrame

using UniRx;
using UnityEngine;
public class UniRxThrottleFirstFrame : MonoBehaviour
{
    void Start()
    {
        Observable.EveryUpdate()
            .Where(_ => Input.GetMouseButtonDown(0))
            .ThrottleFirstFrame(30)
            .Subscribe(_ => Debug.Log("clicked"));
    }
}

运行结果为,每 30 帧内的第一次点击事件输出 clicked。

十三、ThrottleFrame

using UniRx;
using UnityEngine;
public class UniRxThrottleFrameExample : MonoBehaviour
{
    void Start()
    {
        Observable.EveryUpdate()
            .Where(_ => Input.GetMouseButtonDown(0))
            .ThrottleFrame(100)
            .Subscribe(_ => Debug.Log("clicked"));
    }
}

运行结果,鼠标点击的 100 帧内,没有鼠标点击事件,则在 100 帧后输出,否则重新计算帧数。

 十四、TimeoutFrame

using UniRx;
using UnityEngine;
public class UniRxTimeoutFrameExample : MonoBehaviour
{
    void Start()
    {
        Observable.EveryUpdate()
            .Where(_ => Input.GetMouseButtonDown(0))
            .TimeoutFrame(100)
            .Subscribe(_ => Debug.Log("clicked"));
    }
}

运行结果为,超过 100 帧不进行鼠标点击时,报出异常。

十五、TakeUntilDestroy

using UniRx;
using UnityEngine;
public class UniRxTakeUntilDestroyExample : MonoBehaviour
{
    void Start()
    {
        Observable.EveryUpdate()
            .Where(_ => Input.GetMouseButtonDown(0)) 
            .TakeUntilDestroy(this)
            .Subscribe(_ => Debug.Log("mouse clicked"));
    }
}

运行结果为,每次按下鼠标左键,则输出 mouse clicked,将该脚本所在的 GameObject 被销毁后,点击鼠标不再输出。

十六、TakeUntilDisable

using UniRx;
using UnityEngine;
public class UniRxTakeUntilDisableExmaple : MonoBehaviour
{
    void Start()
    {
        Observable.EveryUpdate()
            .Where(_ => Input.GetMouseButtonDown(0))
            .TakeUntilDisable(this)
            .Subscribe(_ => Debug.Log("mouse clicked"));
    }
}

运行结果为,每次按下鼠标左键,则输出 mouse clicked,将 该脚本所在的 GameObject 隐藏掉后,点击鼠标不再输出。

十七、RepeatUntilDisable

using System;
using UniRx;
using UnityEngine;
public class UniRxRepeatUntilDisableExample : MonoBehaviour
{
    void Start()
    {
        Observable.Timer(TimeSpan.FromSeconds(1.0f))
            .RepeatUntilDisable(this)
            .Subscribe(_ => Debug.Log("ticked"));
    }
}

运行结果为,每隔一秒输出 ticked,当把该脚本所在的 GameObject 隐藏,则停止输出。

十八、课程总结

到此呢,我们把 UniRx 6.2.2 版本的所有操作符介绍完了。基本上有了这个课程在手,在开发中遇到了比较难以理解的操作符或者 Observable 就不用害怕了,在本课程都会找到。

我们的 UniRx 第二季课程就结束了,第三季的课程主要偏重的方向是UniRx 的原理及源码分析,及背后的设计模式等介绍,让大家更加透彻地掌握 UniRx 发挥它的最大的威力。

  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,这里给您举一个使用RxJava框架的实例: 假设有一个需求,要求从一个API接口获取用户信息并显示在界面上。我们可以使用RxJava来实现这个需求: 首先,在对应的Activity或Fragment,我们定义一个Observable对象,用来发出网络请求并获取用户信息: ```java Observable<User> userObservable = Api.getUserInfo(userId); ``` 然后,我们可以使用subscribeOn()方法指定请求在IO线程执行,使用observeOn()方法指定结果在主线程回调: ```java userObservable.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Observer<User>() { @Override public void onSubscribe(Disposable d) { // 可以在这里做一些初始化操作,比如显示loading等 } @Override public void onNext(User user) { // 获取到用户信息后,更新UI显示 updateUi(user); } @Override public void onError(Throwable e) { // 出现异常时,可以做一些错误处理,比如弹出Toast提示等 showError(e.getMessage()); } @Override public void onComplete() { // 请求完成后,可以在这里做一些清理工作,比如隐藏loading等 } }); ``` 在上面的代码,我们通过实现Observer接口来处理请求结果。在onNext()方法,我们可以拿到获取到的用户信息,然后更新UI显示。在onError()方法,我们可以处理请求出现异常的情况,比如弹出Toast提示。在onComplete()方法,我们可以做一些清理工作,比如隐藏loading。 这就是一个简单的使用RxJava来获取用户信息的示例。通过使用RxJava,我们可以简化异步编程的复杂度,使代码更加清晰易读。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

林枫依依

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值