链式编程与UniRX的基础使用

第一部分:链式编程

链式方法在C#中为拥有返回值的扩展静态方法

  • C#里面可以写一种特殊的工具方法,专门用来扩展某个类型的使用,任意此类型都可以直接呼出此方法,这种方法称作为扩展方法。
  • 扩展方法必须放在一个非泛型静态类中,一般设置一个工具类来存放即可。
1
2
3
4
5
6
7
8
public static class UtilityExtension//这是一个非泛型静态类,static就是静态修饰符
{
//扩展方法定义示范
public static void ExtensionTest(this GameObject obj)
{
//参数中必须有this修饰符来修饰被扩展类
}
}
1
2
3
4
5
private void Start()//这是Unity某个继承自MonoBehaviour的类的启动方法
{
//扩展方法使用示范
gameObject.ExtensionTest();//扩展方法可以直接呼出,特别方便
}
  • 好了,已经知道了扩展类怎么使用了,那么什么样才是链式编程哪?
  • 很简单,扩展类携带返回值即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static class UtilityExtension
{
//这里为了方便解释什么是链式的传参,将这两个方法写成了一个循环
//前者从GameObject呼出,返回其Transform组件
//后者从Transform呼出,返回其GameObject组件
public static Transform GetTransform(this GameObject obj)
{
return obj.transform;//设置返回值
}
public static GameObject GetGameObject(this Transform trans)
{
return trans.gameObject;//设置返回值
}
}
1
2
3
4
5
6
7
8
9
10
private void Start()//这是Unity某个继承自MonoBehaviour的类的启动方法
{
//扩展方法使用示范
GameObject go = this.gameObject
.GetTransform()//由上游输入GameObject类型,向下游返回Transform类型
.GetGameObject()//由上游输入Transform类型,向下游返回GameObject类型
.GetTransform()//同上
.GetGameObject();//同上
print(go.name);
}
  • 这些方法能像链条一样一个接一个地写下去,与节点式编程的逻辑很像。
  • 它们都有一些相同的特征:
    • 链式方法会从一种数据类型呼出,类似节点的输入。
    • 链式方法会从一种数据类型返回,类似节点的输出。
  • 下面还有一个例子,是关于加减法的,可能更好理解。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static class UtilityExtension
{
//定义了三个数学计算方法
//此处扩展方法返回值与被拓展类型一致,意味着能够互相调用
public static int AddInt(this int self, int fact)//加法
{
return self + fact;
}
public static int SubInt(this int self, int fact)//减法
{
return self - fact;
}
public static int AbsInt(this int self)//绝对值
{
return Mathf.Abs(self);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
private void Start()//这是Unity某个继承自MonoBehaviour的类的启动方法
{
int myInt = 10;//定义一个数值类型
//链式编程的特征是能在一句代码里加数个扩展方法
myInt = myInt//多换行可能有助于阅读
.AddInt(20)//加上20
.SubInt(35)//减去35
.AbsInt()//绝对值
.SubInt(5);//减去5
//|10 + 20 - 35| - 5 = 0
print($"答案为:{myInt}");
}
  • 为了对比与传统方法的区别,我特地附注了传统方法示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static class UtilityExtension//此处没有静态类的限制,图方便就这么写了
{
//定义了三个数学计算方法
public static int AddInt(int self, int fact)//加法(传统版)
{
return self + fact;
}
public static int SubInt(int self, int fact)//减法(传统版)
{
return self - fact;
}
public static int AbsInt(int self)//绝对值(传统版)
{
return Mathf.Abs(self);
}
}
1
2
3
4
5
6
7
8
private void Start()//这是Unity某个继承自MonoBehaviour的类的启动方法
{
int myInt2 = 10;//定义一个数值类型
//传统方法似乎有点不太可读
myInt2 = SubInt(AbsInt(SubInt(AddInt(myInt2, 20), 35)), 5);
//|10 + 20 - 35| - 5 = 0
print($"答案为:{myInt2}");
}

第二部分:UniRX的基础使用

  • 最近终于有时间来了解这个代码集了,因为只自学了两天,所以可能有些条目存在理解问题。
  • UniRX是什么我不会去介绍,因为我不知道这么去给它(Unity的响应式编程)下定义。但是它能实现一些很强的功能,这些功能的使用方式是基于链式编程的。
  • 我将一些基础方法做了分类,先介绍基础使用,再介绍这些单独的方法。

怎么获取UniRX

〇:一些基础前置知识(可跳过)

[前置知识]C# Lambda表达式(匿名方法)

  • 用途:用作一个匿名的方法,如果你不想在脚本底下再定义一个额外的方法的话。
  • Lambda表达式一般写作:()=>{}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
using UnityEngine.Events;//引入命名空间

public UnityEvent ev;//这是一个事件委托

private void Start()//这是Unity某个继承自MonoBehaviour的类的启动方法
{
//为事件委托注册普通方法:
ev.AddListener(TestFunction);
//为事件委托注册匿名方法:(不换行也是可以的)
//()内为匿名函数的传参,无传参就写一个(),=>指向一个{},{}里是方法体
ev.AddListener(() =>
{
Debug.Log("匿名方法");
});
//事件播报:
ev.Invoke();
}

public void TestFunction()//这是普通方法
{
Debug.Log("普通方法");
}
  • 有时候匿名方法里需要传参。
  • 当需要传参时,写作:(int i)=>{}(i)=>{}i=>{}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
using UnityEngine.Events;//引入命名空间

public UnityEvent<float> ev;//这是一个事件委托,携带一个浮点参数

private void Start()//这是Unity某个继承自MonoBehaviour的类的启动方法
{
//为事件委托注册普通方法:
ev.AddListener(TestFunction);
//为事件委托注册匿名方法:(不换行也是可以的)
//只有一个传参的情况下,()可以不用写,如下这种情况也可以写成(float f)
ev.AddListener(f =>
{
Debug.Log($"匿名方法 {f}");
});
//事件播报:
ev.Invoke();
}

public void TestFunction(float f)//这是普通方法,需要传入一个浮点参数
{
Debug.Log($"普通方法 {f}");
}

壹:最简单的UniRX结构

1
2
3
//这些是可能需要的命名空间引入
using UniRx;
using UniRx.Triggers;
1
2
3
4
5
6
7
8
9
10
private void Start()//这是Unity某个继承自MonoBehaviour的类的启动方法
{
Observable.EveryUpdate()//从Mono生命周期生成观察器
.Subscribe(_ =>//观察器订阅执行事件
{
print("TODO");
})
.AddTo(this);//绑定本物体的生命周期
//*注:如果没有绑定任何物体的生命周期,那么这则响应将会一直嵌入内存中,直到Unity编辑器的关闭
}
  • 第一行“Observable.EveryUpdate()”构成了一个观察器,此时返回值类型为IObservable
  • 第二行“.Subscribe(_ =>{print("TODO");})”为此观察器订阅了一个onNext事件,构成了一个释放器,此时返回值类型为IDisposable
  • 第三行“.AddTo(this)”将此释放器的生命周期绑定到本物体上,语句结束。
  • 这个消息语句的目的是:在每帧游戏刷新时输出“TODO”,直到本物体的销毁。
  • 这个消息语句替换为节点化编程类似于:
1
2
3
graph LR
1("EveryUpdate")==>2("Subscribe");
2("Subscribe")==>3("AddTo");

贰:观察者、观察器与释放器

  • 观察者是Observer,接口为IObserver
  • 观察器是Observable,接口为IObservable
  • 释放器是Disposable,接口为IDisposable

【观察者】

  • Subscribe操作符主要干一个功能,将输入的观察器绑定到新建的观察者上,并向观察者输入三状态方法:onNextonCompleteonError
  • 由于观察者一般是操作符自动生成的,所以一般不会有什么特殊操作。

【观察器】

  • 有多种方法可以用来生成观察器,不仅仅只有EveryUpdate一种。
  • 比如说从某游戏物体身上生成的观察器,这样的观察器转换的释放器可以不考虑绑定问题。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
[SerializeField] public GameObject GO_Sample;//外部获取一个游戏物体

private void Start()//这是Unity某个继承自MonoBehaviour的类的启动方法
{
//物体生命周期观察器会自动绑定该物体,不需要使用AddTo去重新绑定
GO_Sample.OnEnableAsObservable()//从当物体可用时生成观察器
.Subscribe(_ => { print("OnEnable"); });
GO_Sample.OnDisableAsObservable()//从当物体禁用时生成观察器
.Subscribe(_ => { print("OnDisable"); });
GO_Sample.OnDestroyAsObservable()//从当物体被删除时生成观察器
.Subscribe(_ => { print("OnDestroy"); });
GO_Sample.UpdateAsObservable()//从物体更新生成观察器(FixedUpdate / LateUpdate)
.Subscribe(_ => { print("Update"); });
//
GO_Sample.OnBecameVisibleAsObservable()//从当物体渲染器组件可用时生成观察器
.Subscribe(_ => { print("OnBecameVisible"); });
GO_Sample.OnBecameInvisibleAsObservable()//从当物体渲染器组件不可用时生成观察器
.Subscribe(_ => { print("OnBecameInvisible"); });
//
GO_Sample.OnMouseDownAsObservable()//从当鼠标按下此物体时那帧生成观察器
.Subscribe(_ => { print("OnMouseDown"); });
GO_Sample.OnMouseDragAsObservable()//从当鼠标持续按下此物体时生成观察器
.Subscribe(_ => { print("OnMouseDrag"); });
GO_Sample.OnMouseEnterAsObservable()//从当鼠标进入此物体时那帧生成观察器
.Subscribe(_ => { print("OnMouseEnter"); });
GO_Sample.OnMouseExitAsObservable()//从当鼠标悬浮此物体之上时生成观察器
.Subscribe(_ => { print("OnMouseExit"); });
GO_Sample.OnMouseOverAsObservable()//从当鼠标移出此物体时那帧生成观察器
.Subscribe(_ => { print("OnMouseOver"); });
GO_Sample.OnMouseUpAsButtonAsObservable()//从当鼠标抬起于此物体时且与按下时的物体相同时那帧生成观察器
.Subscribe(_ => { print("OnMouseUpAsButton"); });
GO_Sample.OnMouseUpAsObservable()//从当鼠标抬起于此物体时那帧生成观察器
.Subscribe(_ => { print("OnMouseUp"); });
//
GO_Sample.OnCollisionEnterAsObservable()//从当物体进入碰撞除了角色控制器时生成观察器(有2D版本)
.Subscribe(_ => { print("OnCollisionEnter"); });
GO_Sample.OnCollisionStayAsObservable()//从当物体持续碰撞除了角色控制器时生成观察器(有2D版本)
.Subscribe(_ => { print("OnCollisionStay"); });
GO_Sample.OnCollisionExitAsObservable()//从当物体离开碰撞除了角色控制器时生成观察器(有2D版本)
.Subscribe(_ => { print("OnCollisionExit"); });
//
GO_Sample.OnTriggerEnterAsObservable()//从当物体进入触发除了角色控制器时生成观察器(有2D版本)
.Subscribe(_ => { print("OnTriggerEnter"); });
GO_Sample.OnTriggerStayAsObservable()//从当物体持续触发除了角色控制器时生成观察器(有2D版本)
.Subscribe(_ => { print("OnTriggerStay"); });
GO_Sample.OnTriggerExitAsObservable()//从当物体离开触发除了角色控制器时生成观察器(有2D版本)
.Subscribe(_ => { print("OnTriggerExit"); });
}
  • 以及由集合类型演变而成的观察器,将每一个数据转化为一个该类型的信号来输出(字典也可以)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private void Start()//这是Unity某个继承自MonoBehaviour的类的启动方法
{
//此处定义数列类型与数组类型
List<int> lst_Int = new List<int>() { 1, 3, 5, 7, 9 };
int[] arr_Int = new int[] { 2, 4, 6, 8, 10 };
//
//集合类型的ToObservable将集合中的每一个数据转化为一个该类型的信号来输出,相当于foreach
lst_Int.ToObservable()
.Subscribe(i =>
{
print($"List {i}");
});
//
arr_Int.ToObservable()
.Subscribe(i =>
{
print($"Array {i}");
});
}
  • UniRX有一些响应化的数值,也可以转换成观察器,仅当值变化时触发事件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public ReactiveProperty<int> RP_Int = new ReactiveProperty<int>();//响应化类型
public ReactiveCollection<int> RC_Int = new ReactiveCollection<int>();//响应化集合
public ReactiveDictionary<string, int> RD_Int = new ReactiveDictionary<string, int>();//响应化字典

private void Start()//这是Unity某个继承自MonoBehaviour的类的启动方法
{
RP_Int.AsObservable()
.Subscribe(i => print("数据变化"));
RC_Int.ObserveAdd()
.Subscribe(lst => print("集合元素增加"));
RC_Int.ObserveRemove()
.Subscribe(lst => print("集合元素减少"));
RC_Int.ObserveCountChanged()
.Subscribe(lst => print("集合元素个数变化"));
RD_Int.ObserveAdd()
.Subscribe(lst => print("字典元素增加"));
RD_Int.ObserveRemove()
.Subscribe(lst => print("字典元素减少"));
RD_Int.ObserveCountChanged()
.Subscribe(lst => print("字典元素个数变化"));
}
  • 有一种泛型拓展方法,可以记录某某类中的某某值是否发生了改变:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[SerializeField] public CharacterController cc;//获取角色控制器
public ReactiveProperty<int> RP_Int = new ReactiveProperty<int>();//响应化类型

private void Start()//这是Unity某个继承自MonoBehaviour的类的启动方法
{
//ObserveEveryValueChanged可以绑定在任何类的任何子属性上,观测此子属性的变化
cc.ObserveEveryValueChanged(cha => cha.isGrounded)
.Subscribe(_ =>
{
print("角色离地或接地");
});
//
RP_Int.ObserveEveryValueChanged(RP_Int => RP_Int.Value)
.Subscribe(_ =>
{
print("值变化");
});
Observable.EveryUpdate()//每一帧去更改RP_Int.Value的值
.Subscribe(_ =>
{
RP_Int.Value++;
})
.AddTo(this);
}

【释放器】

  • 当你使用Subscribe方法将观察器转为释放器后,需要去考虑释放器如何从内存中释放
1
2
3
4
5
6
7
8
9
10
11
private void Start()//这是Unity某个继承自MonoBehaviour的类的启动方法
{
IDisposable dpb = null;//观察器的返回值为IDisposable接口类型
dpb = Observable.EveryUpdate()
.Subscribe(_ =>
{
print("TODO");
dpb.Dispose();//手动释放观察器
});
//在运行的第一帧时,dpb.Dispose()将释放器释放,释放器释放后将不再继续运行
}
  • 释放器有一个独立版本,类型为CompositeDisposable。多个释放器可以被绑定在一起释放。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private void Start()//这是Unity某个继承自MonoBehaviour的类的启动方法
{
//这种应该算作是“合成释放器”,简称垃圾桶
CompositeDisposable com_dpb = new CompositeDisposable();//释放器
// ------------------------
Observable.EveryUpdate()
.Subscribe(_ =>
{
print("TODO1");
com_dpb.AddTo(gameObject);//可以将释放器绑定到某物体上
})
.AddTo(com_dpb);//绑定到释放器
Observable.EveryUpdate()
.Subscribe(_ =>
{
print("TODO2");
com_dpb.Dispose();//手动释放释放器
})
.AddTo(com_dpb);//绑定到释放器
//以下是“合成释放器”的一些基础方法:
com_dpb.Clear();//清空垃圾桶,释放里面的所有释放器,但唯独不释放垃圾桶
com_dpb.Dispose();//释放垃圾桶,释放里面的所有释放器
com_dpb.Add(Observable.EveryUpdate().Subscribe());//单独去添加一个释放器
}

叁:主体类Subject

  • Subject类型既实现了IObserver接口,又实现了IObservable接口。
  • 可以把Subject想象成一个用户自定义的观察器,用户能够随时调用观察器的观察者,而像其他观察器就无法手动调用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public Subject<int> SUB_Int = new Subject<int>();//主体化类型

private void Start()//这是Unity某个继承自MonoBehaviour的类的启动方法
{
//Subject类型订阅事件
SUB_Int
.Subscribe((index) =>
{
SUB_Int.Dispose();
});
//以下是Subject类型常用方法:
SUB_Int.OnNext(0);//执行Subject
SUB_Int.OnCompleted();//结束Subject
SUB_Int.OnError(new Exception());//异常Subject
SUB_Int.AddTo(this);//绑定Subject
SUB_Int.Dispose();//释放Subject
}

肆:事件类操作符(常用)

  • Subscribe:注册事件

  • AddTo:绑定事件

  • Do:前置事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//示例用于演示Subscribe操作符的用法
private void Start()//这是Unity某个继承自MonoBehaviour的类的启动方法
{
Observable.EveryUpdate()
//Do会在Subscribe前执行前置事件,是最普通的事件委托
.Do(_ =>
{
print("Do");
})
//Subscribe方法里最多配置三个事件:
//- onNext执行事件,单纯地执行委托的方法而已
//- OnComplete结束事件,可能需要告诉编译器什么时候观察结束,当执行OnComplete后会自动释放
//- OnError异常事件,仅当前置条件或执行方法里出错时会调用,当执行OnError后会自动释放
.Subscribe(
onNext: u =>
{
print($"OnNext {u}");
},
onCompleted: () =>
{
print($"OnComplete");
},
onError: exc =>
{
print($"OnError {exc}");
})
//AddTo将释放器绑定在this本地类上
//当绑定类销毁时,调用绑定类的OnDestroy()函数,释放器会自动在此释放。
.AddTo(this);
}

伍:流程类操作符(常用)

  • Where:条件判断
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private void Start()//这是Unity某个继承自MonoBehaviour的类的启动方法
{
Observable.EveryUpdate()
//Where操作符:当……时执行,这里是当鼠标左键按下时执行
.Where(_ => Input.GetMouseButtonDown(0))
//Where操作符里的委托方法必须返回一个布尔值来截流余下操作
.Where(_ =>
{
return Input.GetMouseButtonDown(0);
})
.Subscribe(_ =>
{
print("TODO");
})
.AddTo(this);
}
  • OfType:类型判断
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private void Start()//这是Unity某个继承自MonoBehaviour的类的启动方法
{
//新建一个主体类,专门接收object类型物体
Subject<object> SUB_UNIT = new Subject<object>();
//
SUB_UNIT
//int类型属于或继承于object,所以可以使用OfType来过滤
.OfType<object, int>()
.Subscribe(i =>
{
print($"Number {i}");
});
//主体类调用
SUB_UNIT.OnNext(10);
SUB_UNIT.OnNext("20");
SUB_UNIT.OnCompleted();
}
  • Distinct:当与之前输出不同时才输出
1
2
3
4
5
6
7
8
9
10
11
private void Start()//这是Unity某个继承自MonoBehaviour的类的启动方法
{
List<int> lst_Int = new List<int>() { 1, 3, 1, 5, 3 };
//将数列转化为数据流
lst_Int.ToObservable()
.Distinct()
.Subscribe(i =>
{
print($"List {i}");//输出1,3,5
});
}
  • DistinctUntilChanged:当与前一个输出不同时才输出
1
2
3
4
5
6
7
8
9
10
11
private void Start()//这是Unity某个继承自MonoBehaviour的类的启动方法
{
List<int> lst_Int = new List<int>() { 1, 3, 3, 1, 2 };
//将数列转化为数据流
lst_Int.ToObservable()
.DistinctUntilChanged()
.Subscribe(i =>
{
print($"List {i}");//输出1,3,1,2
});
}
  • StartWith:将信息放置在输出之前来执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private void Start()//这是Unity某个继承自MonoBehaviour的类的启动方法
{
List<int> lst_Int = new List<int>() { 1, 2, 3, 4, 5 };
//将数列转化为数据流
lst_Int.ToObservable()
.StartWith(0)
.Subscribe(i =>
{
print($"List {i}");//输出0,1,2,3,4,5
});
//每一帧的数据流
Observable.EveryUpdate()
.Where(_ => Input.GetMouseButtonDown(0))
//无论如何,Startwith永远在第一帧时输出
.StartWith(0)
.Subscribe(l =>
{
print(l.ToString());
})
.AddTo(this);
}

陆:断点类操作符(常用)

  • First:当第一次触发时输出
1
2
3
4
5
6
7
8
9
10
11
12
private void Start()//这是Unity某个继承自MonoBehaviour的类的启动方法
{
Observable.EveryUpdate()
.Where(_ => Input.GetMouseButtonDown(0))
//仅会在第一次执行时输出
.First()
.Subscribe(_ =>
{
print("First");
})
.AddTo(this);
}
  • Last:当最后一次触发时输出(需要使用其他语句来告诉程序何时为最后一次输出)
1
2
3
4
5
6
7
8
9
10
11
12
private void Start()//这是Unity某个继承自MonoBehaviour的类的启动方法
{
List<int> lst_Int = new List<int>() { 1, 2, 3, 4, 5 };
//
lst_Int.ToObservable()
//仅会在最后一次执行时输出
.Last()
.Subscribe(i =>
{
print($"Last {i}");//输出5
});
}
  • Take:输出次数限制(TakeWhile TakeUntil
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
private void Start()//这是Unity某个继承自MonoBehaviour的类的启动方法
{
Observable.EveryUpdate()
.Where(_ => Input.GetMouseButtonDown(0))
//最多会输出5次
.Take(5)
.Subscribe(_ =>
{
print("Take");
})
.AddTo(this);
Observable.EveryUpdate()
//当是条件时执行,如果触发否条件,则余下都不执行
.TakeWhile(_ => !Input.GetMouseButtonDown(0))
.Subscribe(_ =>
{
print("TakeWhile");
})
.AddTo(this);
Observable.EveryUpdate()
//当某观察器否条件时执行,如果触发是条件,则余下都不执行
.TakeUntil(
Observable.EveryUpdate()
.Where(_ => Input.GetMouseButtonDown(0))
)
.Subscribe(_ =>
{
print("TakeUntil");
})
.AddTo(this);
}
  • Skip:输出次数忽略(SkipWhile SkipUntil
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
private void Start()//这是Unity某个继承自MonoBehaviour的类的启动方法
{
Observable.EveryUpdate()
.Where(_ => Input.GetMouseButtonDown(0))
//从触发第5次后才开始输出
.Skip(5)
.Subscribe(_ =>
{
print("Skip");
})
.AddTo(this);
Observable.EveryUpdate()
//当是条件时不执行,如果触发否条件,则余下都执行
.SkipWhile(_ => !Input.GetMouseButtonDown(0))
.Subscribe(_ =>
{
print("SkipWhile");
})
.AddTo(this);
Observable.EveryUpdate()
//当某观察器否条件时不执行,如果触发是条件,则余下都执行
.SkipUntil(
Observable.EveryUpdate()
.Where(_ => Input.GetMouseButtonDown(0))
)
.Subscribe(_ =>
{
print("SkipUntil");
})
.AddTo(this);
}

柒:延时类操作符(常用)

  • Delay:延时(DelayFrame
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private void Start()//这是Unity某个继承自MonoBehaviour的类的启动方法
{
Observable.EveryUpdate()
.Where(_ => Input.GetMouseButtonDown(0))
//需要等2秒钟才能输出
.Delay(System.TimeSpan.FromSeconds(2))
.Subscribe(_ =>
{
print("Delay");
})
.AddTo(this);
Observable.EveryUpdate()
.Where(_ => Input.GetMouseButtonDown(0))
//需要等60帧才能输出
.DelayFrame(60)
.Subscribe(_ =>
{
print("Delay");
})
.AddTo(this);
}
  • NextFrame[观察器]:等待下一帧
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private void Start()//这是Unity某个继承自MonoBehaviour的类的启动方法
{
Observable.EveryUpdate()
.Where(_ => Input.GetMouseButtonDown(0))
.Subscribe(_ =>
{
print(Time.frameCount);
//NextFrame其实会等到下下帧
Observable.NextFrame()
.Subscribe(_ =>
{
print(Time.frameCount);
})
.AddTo(this);
})
.AddTo(this);
}
  • Timer[观察器]:计时器
1
2
3
4
5
6
7
8
9
10
private void Start()//这是Unity某个继承自MonoBehaviour的类的启动方法
{
//从此时开始计时器来生成观察器
Observable.Timer(TimeSpan.FromSeconds(1))
.Subscribe(_ =>
{
print("Timer");
})
.AddTo(this);
}
  • Interval[观察器]:循环计时器
1
2
3
4
5
6
7
8
9
10
private void Start()//这是Unity某个继承自MonoBehaviour的类的启动方法
{
//从此时开始计时器循环来生成观察器
Observable.Interval(TimeSpan.FromSeconds(1))
.Subscribe(_ =>
{
print("Interval");
});
.AddTo(this);
}
  • TimeInterval:获取触发时间间隔
1
2
3
4
5
6
7
8
9
10
11
12
13
private void Start()//这是Unity某个继承自MonoBehaviour的类的启动方法
{
Observable.EveryUpdate()
.Where(_ => Input.GetMouseButtonDown(0))
//获取相邻两次触发之间的间隔
.TimeInterval()
.Subscribe(t =>
{
print($"与上一次触发的时间差 {t.Interval.TotalSeconds}");
print($"整体时间帧数 {t.Value}");
})
.AddTo(this);
}

捌:采样类操作符(常用)

  • Sample:输出采样
1
2
3
4
5
6
7
8
9
10
11
12
private void Start()//这是Unity某个继承自MonoBehaviour的类的启动方法
{
Observable.EveryUpdate()
//设置采样时间,集中从某一段时间的执行中抽取一次来执行
//如果在这么一段时间内没有触发,也就没有采样输出
.Sample(TimeSpan.FromSeconds(1))
.Subscribe(_ =>
{
print("Sample");
})
.AddTo(this);
}
  • Buffer:输出缓存
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private void Start()//这是Unity某个继承自MonoBehaviour的类的启动方法
{
Observable.EveryUpdate()
.Where(_ => Input.GetMouseButtonDown(0))
//请求缓存,会将固定时间内的执行放到缓存数组中,到计时器的周期时执行并返回
.Buffer(TimeSpan.FromSeconds(2.0f))
.Subscribe(lstLong =>
{
//每两秒钟都会执行,但返回的lstLong数组内不一定有值
foreach (long time in lstLong)
{
print($"Buffer{time}");
}
})
.AddTo(this);
}
  • Throttle:结束连续性计时判断
1
2
3
4
5
6
7
8
9
10
11
12
13
private void Start()//这是Unity某个继承自MonoBehaviour的类的启动方法
{
Observable.EveryUpdate()
.Where(_ => Input.GetMouseButtonDown(0))
//连续性判断,设置几秒内为连续
//当超过秒数后没有再次触发时才执行
.Throttle(TimeSpan.FromSeconds(0.5f))
.Subscribe(_ =>
{
print("Trottle");
})
.AddTo(this);
}
  • ThrottleFirst:开始连续性计时判断
1
2
3
4
5
6
7
8
9
10
11
12
13
private void Start()//这是Unity某个继承自MonoBehaviour的类的启动方法
{
Observable.EveryUpdate()
.Where(_ => Input.GetMouseButtonDown(0))
//连续性判断,设置几秒内为连续
//当触发时执行并开启计时器,计时器内再次触发不会执行,直到计时完毕
.ThrottleFirst(TimeSpan.FromSeconds(0.5f))
.Subscribe(_ =>
{
print("TrottleFirst");
})
.AddTo(this);
}
  • Zip[观察器]:打包等待
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private void Start()//这是Unity某个继承自MonoBehaviour的类的启动方法
{
var obs1 = Observable.EveryUpdate()
.Where(_ => Input.GetMouseButtonDown(0));//按下鼠标左键时触发
var obs2 = Observable.EveryUpdate()
.Where(_ => Input.GetMouseButtonDown(1));//按下鼠标右键时触发
//Zip操作符会打包两个观察器
//当观察器A输出第N次时,需要等待到观察器B输出第N次才能输出
obs1.Zip(obs2, (l, r) => Unit.Default)
.Subscribe(_ =>
{
print("Zip");
})
.AddTo(this);
}
  • Timeout:输出间隔超时判断
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private void Start()//这是Unity某个继承自MonoBehaviour的类的启动方法
{
Observable.EveryUpdate()
.Where(_ => Input.GetMouseButtonDown(0))
//当超过时间没有输出时,会向观察者传递一个onError事件
.Timeout(System.TimeSpan.FromSeconds(5.0f))
.Subscribe(
onNext: _ =>
{
print("TimeOut");
},
onError: _ =>
{
print("TimeOutEXC");
}
)
.AddTo(this);
}

玖:异常类操作符(常用)

  • Catch:捕获异常
1
2
3
4
5
6
7
8
9
10
11
12
13
14
private void Start()//这是Unity某个继承自MonoBehaviour的类的启动方法
{
Observable.EveryUpdate()
.Where(_ => Input.GetMouseButtonDown(0))
.Timeout(System.TimeSpan.FromSeconds(5.0f))
//Catch会尝试去捕捉异常,需要返回一个空的观察器
.Catch<long, Exception>(exc =>
{
Debug.LogError("Catch");
return Observable.Empty<long>();
})
.Subscribe()
.AddTo(this);
}
  • OnErrorRetry:当异常出现时执行,然后终止余下操作
1
2
3
4
5
6
7
8
9
10
11
12
13
private void Start()//这是Unity某个继承自MonoBehaviour的类的启动方法
{
Observable.EveryUpdate()
.Where(_ => Input.GetMouseButtonDown(0))
.Timeout(System.TimeSpan.FromSeconds(2.0f))
//当错误被捕捉时,会先进入OnErrorRetry,然后终止余下操作
.OnErrorRetry((Exception ex) =>
{
Debug.LogError("OnErrorRetry");//在无鼠标输入后第2秒钟时输出
})
.Subscribe()
.AddTo(this);
}
  • Retry:当异常出现时跳过,继续余下操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
private void Start()//这是Unity某个继承自MonoBehaviour的类的启动方法
{
Observable.EveryUpdate()
.Where(_ => Input.GetMouseButtonDown(0))
.Timeout(System.TimeSpan.FromSeconds(2.0f))
//Retry会在错误发生后跳过错误,继续余下操作,传参指能容忍到第几个错误
.Retry(3)
.OnErrorRetry((Exception ex) =>
{
Debug.LogError("OnErrorRetry");//在无鼠标输入后第6秒钟时输出
})
.Subscribe()
.AddTo(this);
}

拾:工具类操作符(常用)

  • Merge:合并观察器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private void Start()//这是Unity某个继承自MonoBehaviour的类的启动方法
{
Subject<long> Sub_Long = new Subject<long>();
//
var obs1 = Observable.EveryUpdate()
.Where(_ => Input.GetMouseButtonDown(0));//按下鼠标左键时触发
var obs2 = Observable.EveryUpdate()
.Where(_ => Input.GetMouseButtonDown(1));//按下鼠标右键时触发
//使用Merge能够将施者与被施者绑定,返回绑定后的观察器,Merge里能接数个观察器
Sub_Long.Merge(obs1, obs2)
.Subscribe(_ =>
{
print("Merge");
});
}
  • Amb:执行优先观察器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private void Start()//这是Unity某个继承自MonoBehaviour的类的启动方法
{
var obs1 = Observable.EveryUpdate()
.Where(_ => Input.GetMouseButtonDown(0));//按下鼠标左键时触发
var obs2 = Observable.EveryUpdate()
.Where(_ => Input.GetMouseButtonDown(1));//按下鼠标右键时触发
//使用Amb可以绑定多个观察器
//当其中第一个观察器触发后,取消其余的观察器,之后只运行这一个
Observable.Amb(obs1, obs2)
.Subscribe(_ =>
{
print("Amb");
})
.AddTo(this);
}
  • Aggregate:迭代器,在迭代完成后输出
1
2
3
4
5
6
7
8
9
10
11
12
private void Start()//这是Unity某个继承自MonoBehaviour的类的启动方法
{
List<int> lst_Int1 = new List<int>() { 1, 2, 3, 4, 5 };
//
lst_Int1.ToObservable()
//将历史迭代数据与当前数据作为参数,返回迭代后的数据
.Aggregate((iteration, current) => iteration * current)
.Subscribe(val =>
{
print($"Aggregate {val}");//((((1*2)*3)*4)*5)=120
});
}
  • Scan:迭代器,当每次迭代后输出
1
2
3
4
5
6
7
8
9
10
11
12
private void Start()//这是Unity某个继承自MonoBehaviour的类的启动方法
{
List<int> lst_Int = new List<int>() { 1, 2, 3, 4, 5 };
//
lst_Int.ToObservable()
//将历史迭代数据与当前数据作为参数,返回迭代后的数据
.Scan((iteration, current) => iteration * current)
.Subscribe(val =>
{
print($"Scan {val}");//1, 2, 6, 24, 120
});
}
  • Range[观察器]:将某范围内的数作为输出来触发
1
2
3
4
5
6
7
8
9
10
private void Start()//这是Unity某个继承自MonoBehaviour的类的启动方法
{
//设置初始值-2与长度5,返回[-2, -1, 0, 1, 2]五个数
Observable.Range(-2, 5)
.Scan((iteration, current) => iteration + current)
.Subscribe(val =>
{
print($"Scan {val}");//-2, -3, -3, -2, 0
});
}
  • Repeat:重复执行输入
1
2
3
4
5
6
7
8
9
10
11
private void Start()//这是Unity某个继承自MonoBehaviour的类的启动方法
{
//Repeat将某个物体循环输入N次
Observable.Repeat(10, 5)
.Scan((iteration, current) => iteration * current)
.Subscribe(val =>
{
print($"Repeat {val}");//10, 100, 1000, 10000, 100000
})
.AddTo(this);
}
  • Concat:观察器前后相搭
1
2
3
4
5
6
7
8
9
10
11
12
13
private void Start()//这是Unity某个继承自MonoBehaviour的类的启动方法
{
List<int> lst_Int1 = new List<int>() { 1, 2, 3, 4, 5 };
List<int> lst_Int2 = new List<int>() { 6, 7, 8, 9, 0 };
//
lst_Int1.ToObservable()
//Concat将被施方绑定在施方之后
.Concat(lst_Int2.ToObservable())
.Subscribe(val =>
{
print($"Concat {val}");//1,2,3,4,5,6,7,8,9,0
});
}
  • SelectMany:观察器置换器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private void Start()//这是Unity某个继承自MonoBehaviour的类的启动方法
{
var mouseStream = Observable.EveryUpdate()
.Where(_ => Input.GetMouseButtonDown(0));
var listStream = new List<int>() { 1, 2, 3, 4, 5 }.ToObservable();
//
//每当mouseStream触发时,listStream里的所有元素都会触发一次
//将listStream里的当前元素返回
Observable.SelectMany(mouseStream, listStream)
.Subscribe(i =>
{
print($"SelectMany {i}");//1, 2, 3, 4, 5
})
.AddTo(this);
}
  • WhenAll:当所有触发满足条件时,在结束时触发
1
2
3
4
5
6
7
8
9
10
11
12
13
private void Start()//这是Unity某个继承自MonoBehaviour的类的启动方法
{
IObservable<long> obs1 = Observable.Timer(System.TimeSpan.FromSeconds(1));
IObservable<long> obs2 = Observable.Timer(System.TimeSpan.FromSeconds(2));
//
Observable.WhenAll(obs1, obs2)
.Delay(System.TimeSpan.FromSeconds(1))
.Subscribe(_ =>
{
print("WhenAll");//等待3s后输出
})
.AddTo(this);
}

拾壹:UniRX与消息机制

  • UniRX的消息机名为MessageBroker,使用也是比较简单的。
  • 更为方便的是,消息接收者不需要知道消息发送者是谁。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private void Start()//这是Unity某个继承自MonoBehaviour的类的启动方法
{
//消息发送
Observable.EveryUpdate()
.Where(_ => Input.GetMouseButtonDown(0))
.Subscribe(_ =>
{
MessageBroker.Default.Publish("Hello");//发送字符串消息Hello
})
.AddTo(this);
//消息接收
MessageBroker.Default.Receive<string>()
.SubscribeOnMainThread()//在主线程中接收
.Where(message => message == "Hello")//截流字符串消息Hello
.Subscribe(message =>
{
print($"MessageBroker");
})
.AddTo(this);
}

拾贰:自定义操作符模版

  • 暂且记录以下实现的一个小功能:检测是否在编辑器环境的暂停状态,如果不在编辑器内,则直接通过
  • 因为大量的UniRX的观察器都不依赖Unity的编辑器状态,致使在编辑器暂停时也能继续运行
  • 这个问题会导致不方便测试,所以自制了一个操作符EditorDebug来规避这个情况
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//这是一个非泛型静态类,专门用来存放扩展方法
public static class My_UniRX_Extension
{
//这个方法属于是Where的标准模版了,用法也跟Where一模一样
public static IObservable<T> CustomOperator<T>(this IObservable<T> source, Func<T, bool> conditionalFunc)
{
return new CustomObservable<T>(source, conditionalFunc);
}

//这个方法里使用了Unity的宏,规范了返回值在编辑器内与编辑器外的变化
public static IObservable<T> EditorDebug<T>(this IObservable<T> source)
{
return new CustomObservable<T>(source,
_ =>
{
#if UNITY_EDITOR
return !UnityEditor.EditorApplication.isPaused;
#else
return true;
#endif
});
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
//以下为特地设置的自定义返回观察器,是直接复制改名了Where操作符的定义
//为什么不能使用UniRX现有的类型来返回哪,还不是因为那些值都是内部标识的,不然早用了
namespace UniRx.Operators//为UniRX命名空间进行扩展
{
//自定义链的观察器构建
internal class CustomObservable<T> : OperatorObservableBase<T>
{
readonly IObservable<T> source;
readonly Func<T, bool> predicate;
readonly Func<T, int, bool> predicateWithIndex;

public CustomObservable(IObservable<T> source, Func<T, bool> predicate)
: base(source.IsRequiredSubscribeOnCurrentThread())
{
this.source = source;
this.predicate = predicate;
}

public CustomObservable(IObservable<T> source, Func<T, int, bool> predicateWithIndex)
: base(source.IsRequiredSubscribeOnCurrentThread())
{
this.source = source;
this.predicateWithIndex = predicateWithIndex;
}

protected override IDisposable SubscribeCore(IObserver<T> observer, IDisposable cancel)
{
if (predicate != null)
{
return source.Subscribe(new Custom(this, observer, cancel));
}
else
{
return source.Subscribe(new Custom_(this, observer, cancel));
}
}

//自定义链的观察者构建
class Custom : OperatorObserverBase<T, T>
{
readonly CustomObservable<T> parent;

public Custom(CustomObservable<T> parent, IObserver<T> observer, IDisposable cancel)
: base(observer, cancel)
{
this.parent = parent;
}

public override void OnNext(T value)
{
var isPassed = false;
try
{
isPassed = parent.predicate(value);
}
catch (Exception ex)
{
try { observer.OnError(ex); } finally { Dispose(); }
return;
}

if (isPassed)
{
observer.OnNext(value);
}
}

public override void OnError(Exception error)
{
try { observer.OnError(error); } finally { Dispose(); }
}

public override void OnCompleted()
{
try { observer.OnCompleted(); } finally { Dispose(); }
}
}

class Custom_ : OperatorObserverBase<T, T>
{
readonly CustomObservable<T> parent;
int index;

public Custom_(CustomObservable<T> parent, IObserver<T> observer, IDisposable cancel)
: base(observer, cancel)
{
this.parent = parent;
this.index = 0;
}

public override void OnNext(T value)
{
var isPassed = false;
try
{
isPassed = parent.predicateWithIndex(value, index++);
}
catch (Exception ex)
{
try { observer.OnError(ex); } finally { Dispose(); }
return;
}

if (isPassed)
{
observer.OnNext(value);
}
}

public override void OnError(Exception error)
{
try { observer.OnError(error); } finally { Dispose(); }
}

public override void OnCompleted()
{
try { observer.OnCompleted(); } finally { Dispose(); }
}
}
}
}