响应式编程框架初使用

Posted 码码小虫

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了响应式编程框架初使用相关的知识,希望对你有一定的参考价值。

题外话

好久没有分享博客了,果然燥热的夏天最容易使人懒惰(其实是自己懒)。最近学习了一些新的东西,.net Core、GRPC、响应式编程之类的,会在之后的博客分享中,将这些东西和Unity串起来,一起分享给大家。好了,废话不多说,进入本次分享的主题,Unity响应式编程框架UniRX,可在Unity Asset Store 中下载。

响应式编程

什么是响应式编程呢?响应式编程是一种面向数据流和变化传播的编程范式。这意味着可以在编程语言中很方便地表达静态或动态的数据流,而相关的计算模型会自动将变化的值通过数据流进行传播。

模拟攻击敌人

首先看下面这样一个例子,通过点击按钮减少当前的血量值,实时更新当前的血量值并显示在屏幕上,当血量值低于0时,宣告死亡,并禁用攻击按钮。

使用传统命令式编程如下:

 
   
   
 
  1. using UnityEngine;

  2. using UnityEngine.UI;

  3. public class TraditionCommand : MonoBehaviour

  4. {

  5. public Button button;

  6. public Text Hptext;

  7. public Text btnText;


  8. Enemys enemy = new Enemys(1000);

  9. void Start()

  10. {

  11. Hptext.text = "满血";

  12. button.onClick.AddListener(() =>

  13. {

  14. enemy.CurrentHp -= 100;

  15. Hptext.text = Hptext.text = "当前血量值为:" + enemy.CurrentHp.ToString();

  16. if (enemy.CurrentHp <= 0)

  17. {

  18. Hptext.text = "血量为零,GameOver";

  19. btnText.text = "敌人已死亡";

  20. button.interactable = false;

  21. }

  22. });

  23. }

  24. }

  25. public class Enemys

  26. {

  27. public int CurrentHp { get; set; }

  28. public bool IsDead { get; set; }


  29. public Enemys(int initHp)

  30. {

  31. CurrentHp = initHp;

  32. IsDead = false;

  33. }

  34. }

用响应式编程的方式来改写代码如下:

 
   
   
 
  1. using UnityEngine;

  2. using UniRx;

  3. using UnityEngine.UI;

  4. public class Test : MonoBehaviour

  5. {

  6. public Button button;

  7. public Text text;

  8. public Text btnText;

  9. Enemy enemy = new Enemy(1000);

  10. void Start()

  11. {

  12. text.text = "满血";

  13. button.OnClickAsObservable().Subscribe(_ =>

  14. {

  15. enemy.CurrentHp.Value -= 100;

  16. text.text = "当前血量值为:" + enemy.CurrentHp.Value.ToString();

  17. });


  18. enemy.IsDead.Where(isDead => isDead == true)

  19. .SubscribeToText(text, _ =>

  20. {

  21. button.interactable = false;

  22. btnText.text = "敌人已死亡";

  23. return "血量为零,GameOver";

  24. });

  25. }

  26. }

  27. public class Enemy

  28. {

  29. public ReactiveProperty<long> CurrentHp { get; set; }

  30. public ReadOnlyReactiveProperty<bool> IsDead { get; set; }


  31. public Enemy(long initHp)

  32. {

  33. CurrentHp = new ReactiveProperty<long>(initHp);

  34. IsDead = CurrentHp.Select(x => x <= 0).ToReadOnlyReactiveProperty();

  35. }

  36. }

两种编程模式均实现了相同的功能;在这个例子中二者的代码量可能相差不多;但仔细研究下,你可能会觉得,响应式编程写出的代码更具有目的性一些。

在传统命令式编程中:

敌人血量的变化,以及血量值得更新显示,等逻辑的处理,均在Button的Click事件中处理。逻辑并不清晰。

在响应式编程中:

1.点击按钮,需要改变当前敌人的血量值,同时更新血量显示。

2.当血量值小于等于0时,需要宣告敌人死亡,并禁用攻击按钮。

这里存在两个主题,一个是攻击按钮,一个是敌人的血量;两者均作为可观察的对象Observable;当二者状态发生改变时,通知观察者Observer,观察者触发相应的订阅Subscribe,执行对应的事件。

再来看一个实现双击事件的例子:

使用传统命令式编程:

 
   
   
 
  1. using System;

  2. using System.Collections;

  3. using System.Collections.Generic;

  4. using UnityEngine;


  5. public class TraditionCommandDoubleClcik : MonoBehaviour

  6. {

  7. public float timeInterval = 0.5f;

  8. public float lastClickTime = 0;

  9. public Action DoubleClickAction;

  10. void Start()

  11. {

  12. lastClickTime = Time.realtimeSinceStartup;

  13. }

  14. void Update()

  15. {

  16. if (Input.GetMouseButtonUp(0))

  17. {

  18. if (Time.realtimeSinceStartup - lastClickTime < timeInterval)

  19. {

  20. DoubleClickAction?.Invoke();

  21. Debug.Log("双击");

  22. }

  23. lastClickTime = Time.realtimeSinceStartup;

  24. }

  25. }

  26. }

使用响应式编程:

 
   
   
 
  1. using System;

  2. using UnityEngine;

  3. using UniRx;

  4. public class ReactiveDoubleClcik : MonoBehaviour

  5. {

  6. void Start()

  7. {

  8. var clickStream = Observable.EveryUpdate()

  9. .Where(_ => Input.GetMouseButtonDown(0));


  10. clickStream

  11. .Buffer(clickStream.Throttle(TimeSpan.FromMilliseconds(250)))

  12. .Where(xs => xs.Count >= 2)

  13. .Subscribe(xs =>

  14. {

  15. Debug.Log("双击");

  16. });

  17. }

  18. }

在传统命令式编程中:

双击:鼠标在给定时间内按下两次或者以上的行为。当鼠标第一次抬起时,记录下鼠标第一次抬起时的时间;当鼠标第二次抬起时记录抬起时的时间;用第二次抬起时的时间减去第一次抬起时的时间,如果这个插值在我们给定的限制时间内,那么,就判定为双击。在这个过程中,我们需要分别记录鼠标在不同时刻抬起时的时间并作插值比较。

在响应式编程中

首先将游戏的循环(Update)作为一个事件流,鼠标点击的这个过程也作为一个流来处理。基于时间来合并处理两个流。分析一下这个代码:

 
   
   
 
  1. var clickStream = Observable.EveryUpdate()

  2. .Where(_ => Input.GetMouseButtonDown(0));

将鼠标按下事件作为一个流。

 
   
   
 
  1. clickStream.Throttle(TimeSpan.FromMilliseconds(250))

节流阀,指定流的下一次(OnNext)触发在给定时间(TimeSpan.FromMilliseconds(250))到达之后。

 
   
   
 
  1. clickStream

  2. .Buffer(clickStream.Throttle(TimeSpan.FromMilliseconds(250)))

将给定流事件缓存在一个数组中,定期将观察到的事件缓存到数组中,根据当前数组中缓存的事件流的个数来判断是否满足双击的需求。如下图:

当然,Unity的响应式编程框架UniRX还包含许多有趣的功能,与传统的编程思维方式有些不同,个人认为UniRX这类框架特别适用于处理数据量变化比较频繁,且实时要求性较好的应用;从传统的编程思维中转变过来也需要一定的时间和练习。

参看资料:

1.https://mcxiaoke.gitbooks.io/rxdocs/content/

2.http://reactivex.io/documentation/operators.html

阅读一遍官方例子,了解流式编程的方式;传统的面向对象方式(OOP)————一切皆对象。响应式编程————一切事件皆为流,对流进行组合、过滤、筛选、处理。


以上是关于响应式编程框架初使用的主要内容,如果未能解决你的问题,请参考以下文章

html 将以编程方式附加外部脚本文件的javascript代码片段,并按顺序排列。用于响应式网站,其中ma

RAC初体验

响应式编程的实践

Java响应式编程 Springboot WebFlux基础与实战

IOS响应式编程框架ReactiveCocoa(RAC)使用示例-备

理解响应式编程