响应式编程框架初使用

题外话

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

响应式编程

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

模拟攻击敌人

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

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

using UnityEngine;
using UnityEngine.UI;
public class TraditionCommand : MonoBehaviour
{
    public Button button;
    public Text Hptext;
    public Text btnText;

    Enemys enemy = new Enemys(1000);
    void Start()
    {
        Hptext.text = "满血";
        button.onClick.AddListener(() =>
        {
            enemy.CurrentHp -= 100;
            Hptext.text = Hptext.text = "当前血量值为:" + enemy.CurrentHp.ToString();
            if (enemy.CurrentHp <= 0)
            {
                Hptext.text = "血量为零,GameOver";
                btnText.text = "敌人已死亡";
                button.interactable = false;
            }
        });
    }
}
public class Enemys
{
    public int CurrentHp { get; set; }
    public bool IsDead { get; set; }

    public Enemys(int initHp)
    {
        CurrentHp = initHp;
        IsDead = false;
    }
}

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

using UnityEngine;
using UniRx;
using UnityEngine.UI;
public class Test : MonoBehaviour
{
    public Button button;
    public Text text;
    public Text btnText;
    Enemy enemy = new Enemy(1000);
    void Start()
    {
        text.text = "满血";
        button.OnClickAsObservable().Subscribe(_ =>
        {
            enemy.CurrentHp.Value -= 100;
            text.text = "当前血量值为:" + enemy.CurrentHp.Value.ToString();
        });

        enemy.IsDead.Where(isDead => isDead == true)
        .SubscribeToText(text, _ =>
        {
            button.interactable = false;
            btnText.text = "敌人已死亡";
            return "血量为零,GameOver";
        });
    }
}
public class Enemy
{
    public ReactiveProperty<long> CurrentHp { get; set; }
    public ReadOnlyReactiveProperty<bool> IsDead { get; set; }

    public Enemy(long initHp)
    {
        CurrentHp = new ReactiveProperty<long>(initHp);
        IsDead = CurrentHp.Select(x => x <= 0).ToReadOnlyReactiveProperty();
    }
}

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

在传统命令式编程中:

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

在响应式编程中:

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

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

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

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

使用传统命令式编程:

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

public class TraditionCommandDoubleClcik : MonoBehaviour
{
    public float timeInterval = 0.5f;
    public float lastClickTime = 0;
    public Action DoubleClickAction;
    void Start()
    {
        lastClickTime = Time.realtimeSinceStartup;
    }
    void Update()
    {
        if (Input.GetMouseButtonUp(0))
        {
            if (Time.realtimeSinceStartup - lastClickTime < timeInterval)
            {
                DoubleClickAction?.Invoke();
                Debug.Log("双击");
            }
            lastClickTime = Time.realtimeSinceStartup;
        }
    }
}

使用响应式编程:

using System;
using UnityEngine;
using UniRx;
public class ReactiveDoubleClcik : MonoBehaviour
{
    void Start()
    {
        var clickStream = Observable.EveryUpdate()
        .Where(_ => Input.GetMouseButtonDown(0));

        clickStream
        .Buffer(clickStream.Throttle(TimeSpan.FromMilliseconds(250)))
        .Where(xs => xs.Count >= 2)
        .Subscribe(xs =>
        {
            Debug.Log("双击");
        });
    }
}

在传统命令式编程中:

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

在响应式编程中

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

var clickStream = Observable.EveryUpdate()
    .Where(_ => Input.GetMouseButtonDown(0));

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

clickStream.Throttle(TimeSpan.FromMilliseconds(250))

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

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

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

Sun.ME

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

官方地址:https://github.com/neuecc/UniRx

参看资料:

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

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

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

更多内容欢迎访问:


你可能感兴趣的:(响应式编程框架初使用)