细节:
同一级(彼此之间没有大括号包含关系)中lambda表达式的参数是互通的
不同级之间lambda表达式的参数不能重名
只有订阅(加Subscribe)之后事件才能发生
JsonUtility不支持ReactiveCollection类型,可以用List,然后再转换为ReactiveCollection,
但转换完之后List内容的改变不能触发ReactiveCollection内容改变的相应方法,所以转换之后不要加监听
可以用ObserveEveryValueChanged来监听列表的某一个属性是否改变以完善上面的功能,示例如下:
Model.TodoItems.ObserveEveryValueChanged(todoItems => todoItems.Count).Subscribe(_ =>
{
OnDataChanged();
});
一.基本:
一些常用的用法:
using UnityEngine;
using UniRx;
namespace UniRxLesson
{
public class IntroExample : MonoBehaviour
{
//Subscribe的参数是一个lambda表达式,因为参数没有作用,所以用_表示(可以理解为一种习惯,实际上_和普通参数没有区别)
void Start()
{
//监听第一次点击按钮的事件,并执行相应的回调方法
Observable.EveryUpdate() // Observable是一个形容词,表示后面的EveryUpdate是可观察的,EveryUpdate时事件的发布者,它会每帧发送一个事件
.Where(_ => Input.GetMouseButtonDown(0)) // 进行一个鼠标是否抬起的判断相对于if(Input.GetMouseButtonDown(0)),Where是在事件的发布者和接收者之间做一个过滤操作,过滤掉不满足条件的事件
.First() // 过滤操作,只获取第一次的点击事件,也可以不用Where直接在First中写上面的方法,也就是相当于在条件判断的基础上加入了只执行一次的限制,次数大于一的操作会被过滤掉
.Subscribe(_ => // 订阅/处理事件,Subscribe是事件的接收者,接受的是EveryUpdate发送的事件
{
Debug.Log("Mouse clicked");
})
.AddTo(this);//当当前脚本或者游戏物体被销毁时此命令不再执行,防止空指针
//实现等待5秒后执行相应方法
//TimeSpan结构,表示一个时间间隔
Observable.Timer(TimeSpan.FromSeconds(5.0f))
.Subscribe(_ =>
{
Debug.Log("Do something");
});
//Merge用法
var leftClickEvents = Observable.EveryUpdate().Where(_ => Input.GetMouseButtonDown(0));
var rightClickEvents = Observable.EveryUpdate().Where(_ => Input.GetMouseButtonDown(1));
Observable.Merge(leftClickEvents, rightClickEvents)//合并两个事件
.Subscribe(_ =>
{
Debug.Log("Mouse clicked");
});
//Select的用法见MVP模式
}
}
}
UI方面:
using UnityEngine;
using UnityEngine.UI;
using UniRx.Triggers;
using UniRx;
namespace UniRxLesson
{
//UniRx对UI的支持
public class UIExample : MonoBehaviour
{
void Start()
{
//button
var button = transform.Find("Button").GetComponent<Button>();
button.OnClickAsObservable()
.Subscribe(_ => Debug.Log("Button clicked"));
//toggle
var toggle = transform.Find("Toggle").GetComponent<Toggle>();
toggle.OnValueChangedAsObservable()
.Where(on => on)
.Subscribe(on => Debug.Log(on));
//image
var image = transform.Find("Image").GetComponent<Graphic>();
//注意这三个方法最好一起用
image.OnBeginDragAsObservable()
.Subscribe(_ => Debug.Log("Begin drag"));
image.OnDragAsObservable()
.Subscribe(_ => Debug.Log("Dragging"));
image.OnEndDragAsObservable()
.Subscribe(_ => Debug.Log("End drag"));
image.OnPointerClickAsObservable()
.Subscribe(_ =>
{
Debug.Log("On pointer click");
});
}
}
}
ReactiveProperty:
using System;
using System.Collections;
using System.Collections.Generic;
using UniRx;
using UnityEngine;
public class ReactivePropertyExample : MonoBehaviour
{
//改变一个变量的值+调用回调方法
//委托写法:
//public Action<int> OnAgeChanged = null;
//private int mAge = 0;
//public int Age
//{
// get
// {
// return mAge;
// }
// set
// {
// if (mAge != value)
// {
// mAge = value;
// if (OnAgeChanged != null)
// {
// OnAgeChanged(value);
// }
// }
// }
//}
//void Start()
//{
// OnAgeChanged += age =>
// {
// Debug.Log("Value changed: " + age);
// };
// Age = 10;
//}
//UniRx写法:
//public ReactiveProperty<int> Age = new ReactiveProperty<int>(0);//默认赋值是在事件订阅之后完成的
//不用泛型也可以这样写,这种写法可以序列化
public IntReactiveProperty Age = new IntReactiveProperty(10);//默认赋值是在事件订阅之前完成的
void Start()
{
Age.Subscribe(age =>
{
Debug.Log("Value changed: " + age);
});
Age.Value = 10;
}
}
总结:实现一个简单的MVP模式:
UniRx实现MVP模式的原理图:
using UniRx;
using UnityEngine;
using UnityEngine.UI;
namespace UniRxLesson
{
//View Hierarchy
//Controller P(Presenter)
public class EnemyExample : MonoBehaviour
{
EnemyModel mEnemy = new EnemyModel(200);
void Start()
{
var attackBtn = transform.Find("Button").GetComponent<Button>();
var HPText = transform.Find("Text").GetComponent<Text>();
attackBtn.OnClickAsObservable()
.Subscribe(_ =>
{
mEnemy.HP.Value -= 99;
});
mEnemy.HP.SubscribeToText(HPText);
mEnemy.IsDead
.Where(isDead => isDead)
.Select(isDead => !isDead)//表示类型转换或者同类型值变换
.SubscribeToInteractable(attackBtn);
}
}
//Model
public class EnemyModel
{
public ReactiveProperty<long> HP;
public IReadOnlyReactiveProperty<bool> IsDead;
public EnemyModel(long initialHP)
{
HP = new ReactiveProperty<long>(initialHP);
IsDead = HP.Select(hp => hp <= 0).ToReactiveProperty();
}
}
}
二.入门:
一些常用用法:
//下面方法具体的执行顺序可以在引擎中实践得出
Observable.EveryUpdate().Subscribe(_ => { Debug.Log("EveryUpdate"); });
Observable.EveryFixedUpdate().Subscribe(_ => { Debug.Log("EveryFixedUpdate"); });
Observable.EveryEndOfFrame().Subscribe(_ => { Debug.Log("EveryEndOfFrame"); });
Observable.EveryLateUpdate().Subscribe(_ => { Debug.Log("EveryLateUpdate"); });
Observable.EveryAfterUpdate().Subscribe(_ => { Debug.Log("EveryAfterUpdate"); });
//当前焦点是否位于本应用
Observable.EveryApplicationFocus().Subscribe(focus =>
{
Debug.Log(focus);
});
//时间戳的用法,显示的时间为0时区的时间
var reactiveCommand = new ReactiveCommand<int>();
reactiveCommand.Where(x => x % 2 == 0).Subscribe(x => Debug.LogFormat("{0} 是 偶数", x));
reactiveCommand.Where(x => x % 2 != 0).Timestamp().Subscribe(x => Debug.LogFormat("{0} 是 奇数 {1}", x.Value, x.Timestamp));
reactiveCommand.Execute(10);
reactiveCommand.Execute(11);
全局与非全局的区别:
using UnityEngine;
using UniRx;
using UniRx.Triggers;
namespace UniRxLesson
{
public class UnityAPIExample : MonoBehaviour
{
void Start()
{
//全局的,当前游戏物体销毁后依然执行
//Observable.EveryUpdate().Subscribe(_ =>
//{
// Debug.Log("EveryUpdate");
//});
//非全局的,当前游戏物体销毁后不执行,和Addto类似
this.UpdateAsObservable().Subscribe(_ =>
{
Debug.Log("EveryUpdate");
});
//实际上Addto为下面方法的封装
this.OnDestroyAsObservable()
.Subscribe(_ =>
{
Debug.Log("On destroy");
});
}
}
}
Coroutine:
//将事件源(Observable)转化为一个Coroutine中的yield对象
public class Observable2YieldExample : MonoBehaviour
{
IEnumerator Delay1Second()
{
yield return Observable.Timer(TimeSpan.FromSeconds(1.0f)).ToYieldInstruction();//转换
Debug.Log("A");
}
void Start()
{
StartCoroutine(Delay1Second());
}
}
//使用UniRx将Coroutine转换为事件源
public class CoroutineExample : MonoBehaviour
{
IEnumerator CoroutineA()
{
yield return new WaitForSeconds(1.0f);
Debug.Log("A");
}
void Start()
{
//StartCoroutine(CoroutineA());
Observable.FromCoroutine(_ => CoroutineA())//转换
.Subscribe(_ =>
{
});//注意这里一定要订阅,否则事件不会发生
}
}
//将Coroutine转换为事件源的好处在于可以借助UniRx使协程更加强大,如实现协程并行
//协程并行,即只有两个协程都执行完才进行相应的方法
public class CoroutineSpawnExample : MonoBehaviour
{
IEnumerator A()
{
yield return new WaitForSeconds(1.0f);
Debug.Log("A");
}
IEnumerator B()
{
yield return new WaitForSeconds(2.0f);
Debug.Log("B");
}
void Start()
{
var aStream = Observable.FromCoroutine(_ => A());//括号中直接写A也行,两种写法一致
var bStream = Observable.FromCoroutine(_ => B());
Observable.WhenAll(aStream, bStream)//主要就是whenAll关键字
.Subscribe(_ =>
{
Debug.Log("When all A and B");
});
}
}
//WhenAll不仅仅只用于协程的并行
//实现所有按钮都被点击之后触发相应方法的功能
public class AllButtonClickedOnceExample : MonoBehaviour
{
void Start()
{
var leftMouseClickedEvents = Observable.EveryUpdate().Where(_ => Input.GetMouseButtonDown(0)).First();//注意事件一定要是可以被完成的,这里的First不可以去掉
var rightMouseClickedEvents = Observable.EveryUpdate().Where(_ => Input.GetMouseButtonDown(1)).First();
Observable.WhenAll(leftMouseClickedEvents, rightMouseClickedEvents)
.Subscribe(_ =>
{
Debug.Log("Mouse clicked");
});
}
}
//关于WhenAll为什么可以获得一个事件的完成与否其实是通过Subscribe的第二个参数实现的
//Subscribe方法的第一个参数表示下一个事件的对应方法(如在EveryUpdate中每一帧都会执行这个方法,而在计时协程等相同功能的实现中只是到时间了才会执行,详情见下面的代码),
//第二个参数表示事件完成对应的方法,要求事件具有可完成的特性
public class OnCompletedExample : MonoBehaviour
{
void Start()
{
//Observable.Timer(TimeSpan.FromSeconds(1.0f)).Subscribe(_ =>
//{
// Debug.Log("OnNext:after 1 second");
//}, () =>
//{
// Debug.Log("OnCompleted");
//});
//EveryUpdate及加AddTo的EveryUpdate都没有完成的特性
Observable.FromCoroutine(A)
.Subscribe(_ =>
{
Debug.Log("OnNext:");
}, ()=>
{
Debug.Log("OnCompleted:");
});
}
IEnumerator A()
{
yield return new WaitForSeconds(2.0f);
}
}
线程:
var threadAStream = Observable.Start(() => //开启一个线程
{
Thread.Sleep(TimeSpan.FromSeconds(1));
return 10;
});
var threadBStream = Observable.Start(() =>
{
Thread.Sleep(TimeSpan.FromSeconds(3));
return 10;
});
Observable.WhenAll(threadAStream, threadBStream)
.ObserveOnMainThread() //当上边两个线程结束则把值传回到主线程
.Subscribe(results =>
{
Debug.LogFormat("{0}:{1}", results[0], results[1]);
});
WWW的支持:
ObservableWWW.Get("http://sikiedu.com") //通过url获取一个网页的内容
.Subscribe(responseText =>
{
Debug.Log(responseText);
}, e => //发生错误便于查错,可以不加这个参数
{
Debug.LogError(e);
});
var sikieduStream = ObservableWWW.Get("http://www.sikiedu.com/");
var baiduStream = ObservableWWW.Get("https://www.baidu.com/");
//使用WhenAll
Observable.WhenAll(sikieduStream, baiduStream)
.Subscribe(responseTexts =>
{
Debug.Log(responseTexts[0].Substring(0, 100));
Debug.Log(responseTexts[1].Substring(0, 100));
}, e =>
{
Debug.LogError(e);
});
//下载资源并查看进度
var progressObservable = new ScheduledNotifier<float>(); //查看进度的变量
ObservableWWW.GetAndGetBytes("xxx", progress: progressObservable) //从xxxurl下载资源,进度存储到上面的变量,:用来指定与实参匹配的形参
.Subscribe(bytes =>
{
//这里常用来指定资源下载到哪里
});
progressObservable.Subscribe(progress => //输出进度
{
Debug.LogFormat("进度为:{0}", progress);
});
ReactiveCommand:
//ReactiveCommand有一个只读属性CanExecute和一个可读写的方法Execute,当CanExecute为true时方法的调用才能执行,否则不会执行
var reactiveCommand = new ReactiveCommand(); //默认情况下CanExecute为true
reactiveCommand.Subscribe(_ =>
{
Debug.Log("Execute");
}); //注册好Execute方法
reactiveCommand.Execute();//调用
reactiveCommand.Execute();
reactiveCommand.Execute();
//下面是一个实例:
public class MouseUpDownExample : MonoBehaviour
{
void Start()
{
var mouseDownStream = Observable.EveryUpdate().Where(_ => Input.GetMouseButton(0)).Select(_ => true);
var mouseUpStream = Observable.EveryUpdate().Where(_ => Input.GetMouseButtonUp(0)).Select(_ => false);
var isMouseUp = Observable.Merge(mouseDownStream, mouseUpStream);
var reactiveCommand = new ReactiveCommand(isMouseUp, false); //这里是设置了事件源和CanExecute的默认值
reactiveCommand.Subscribe(_ =>
{
Debug.Log("Reactive command executed");
});
Observable.EveryUpdate().Subscribe(_ =>
{
reactiveCommand.Execute();
);
}
}
ReactiveCollection和ReactiveDictionary:
//Collection相当于List
public class ReactiveCollectionExample : MonoBehaviour
{
ReactiveCollection<string> mNames = new ReactiveCollection<string>
{
"name1",
"name3"
}; //声明并初始化
void Start()
{
foreach (var url in mNames)
{
Debug.Log(url);
} //遍历
mNames.ObserveAdd().Subscribe(addUrl => Debug.LogFormat("Add:{0}", addUrl)); //监听添加事件
mNames.ObserveRemove().Subscribe(removedUrl => Debug.LogFormat("Remove:{0}", removedUrl)); //监听移除事件
mNames.ObserveCountChanged().Subscribe(count => Debug.LogFormat("Count:{0}", count)); //监听数量改变事件
//相应操作
mNames.Add("name2");
mNames.Remove("name3");
}
}
//Dictionary
public class ReactiveDictionaryExample : MonoBehaviour
{
ReactiveDictionary<string, string> mLanguageCode = new ReactiveDictionary<string, string>
{
{ "En", "英文"},
{ "Cn", "中文"}
}; //声明并初始化
void Start()
{
mLanguageCode.ObserveAdd().Subscribe(addedLanguage => Debug.LogFormat("Add:{0}", addedLanguage)); //监听增加事件
mLanguageCode.ObserveRemove().Subscribe(removedLanguage => Debug.LogFormat("Remove:{0}", removedLanguage)); //监听移除事件
mLanguageCode.ObserveCountChanged().Subscribe(count => Debug.LogFormat("count:{0}", count)); //监听数量改变事件
//操作
mLanguageCode.Add("Jp", "日文");
mLanguageCode.Remove("En");
}
}