• 熱門專題

Unity基于響應式編程(Reactive programming)入門

作者:伊利丹·怒風  發布日期:2016-11-30 20:23:49
  •  

    背景

    前有慕容小匹夫的一篇《解構C#游戲框架uFrame兼談游戲架構設計》,引用文中內容

    uFrame是提供給3D開發者使用的一個框架插件,它本身模仿了MVVM這種架構模式(事實上并不包含Model部分,且多出了Controller部分)。因為用于Unity3D,所以它向開發者提供了一套基于Editor的可視化編輯工具,可以用來管理代碼結構等。需要指出的是它的一個重要的理念,同時也是軟件工程中的一個重要理念就是關注分離(Separation of concern,SoC)。uFrame借助控制反轉(IoC)/依賴注入(DI)實現了這種分離,從而進一步實現了MVVM這種模式。且在1.5版本之后,引入了UniRx庫,引進了響應式編程的思想。

    讀起來高大上,本文主要想從實際出發,著手最后一句“且在1.5版本之后,引入了UniRx庫,引進了響應式編程的思想。”,在Unity中如何使用響應式編程,如何使用UniRx庫。

    當然一下列出這么多新概念性的東西,作為新手必然理解起來有困難的,當然我也希望你是天賦迥異的人。這里列出幾點,如果你不了解,請自行去學習或者復習,回來在看也不遲。

    1、Linq基礎,Linq的本質及與傳統命令式編程的區別和優點

    2、聲明式編程和命令式編程的概念和區別

    3、什么是響應式編程

    4、什么是觀察者模式

    5、軟件編程中Stream的概念

    好了裝b時間過去了,讓我們簡單的說下什么是響應式編程。這里也不廢話,引用一段,看的懂得自然明白,不懂得還是不明白

    什么是反應式編程:反應式編程(Reactive programming)簡稱Rx,他是一個使用LINQ風格編寫基于觀察者模式的異步編程模型。簡單點說Rx = Observables + LINQ + Schedulers。

    這里為什么要在游戲開發中引入響應式編程Rx,答案是游戲特別適合RX編程,因為在游戲中廣泛應用了時間(幀)和事件(UI)的概念,時間本身是一種流,而事件也是基于時間的一種信號(并不是特別準確,意會),而這正是RX所擅長的。

    600216-20151118151734936-2055036453

    實現

    本文以系列文章中的精靈鼠標移動和序列幀動畫為基礎,沒有基礎的先參考下傳統實現方式一下兩篇文章

    時光煮雨 Unity3D實現2D人物動畫① UGUI&Native2D序列幀動畫

    時光煮雨 Unity3D實現2D人物動畫② Unity2D 動畫系統&資源效率

    這里引入了UniRx庫,來實現基于響應式編程及聲明式編程代碼重構,代碼如下:

    using UnityEngine;
    using UniRx;

    public class PlayerController : MonoBehaviour
    {
        public float speed;
        private Vector3 moveDirection;

        private int currentTexture = 0;
        public Sprite[] textureArray;
        // Use this for initialization
        void Start()
        {
            //鼠標控制移動,每幀更新
            Observable.EveryUpdate()
             .Subscribe(_ =>
             {
                 //1、獲得當前位置
                 Vector3 curenPosition = this.transform.position;
                 //2、獲得方向
                 if (Input.GetButton('Fire1'))
                 {
                     Vector3 moveToward = Camera.main.ScreenToWorldPoint(Input.mousePosition);

                     moveDirection = moveToward - curenPosition;
                     moveDirection.z = 0;
                     moveDirection.Normalize();
                 }
                 //3、插值移動
                 Vector3 target = moveDirection * speed + curenPosition;
                 transform.position = Vector3.Lerp(curenPosition, target, Time.deltaTime);
             });

            //幀動畫
            SpriteRenderer spriteRenderer = GetComponent<SpriteRenderer>();
            //定時器每隔5幀
            Observable.IntervalFrame(5).Subscribe(_ =>
            {
                currentTexture++;
                if (currentTexture >= textureArray.Length)
                {
                    currentTexture = 0;
                }
                spriteRenderer.sprite = textureArray[currentTexture];
            });
        }

    }
    是的沒有看錯,你沒有發現熟悉的Update函數,如果說以上函數讓你看到就是把所有代碼就放在了Start里面而已,我們再重構一下代碼,使用提取方法,看看效果,這就是聲明式編程的魅力,程序可讀性增強,更適合人類的思維方式

    using UnityEngine;
    using UniRx;

    public class PlayerController : MonoBehaviour
    {
        public float speed;
        private Vector3 moveDirection;

        private int currentTexture = 0;
        public Sprite[] textureArray;
        // Use this for initialization
        void Start()
        {
            //鼠標控制移動,每幀更新
            PlayerMove();

            //角色 幀動畫
            PlayerAnimation();
        }

        /// <summary>
        /// 角色 幀動畫控制
        /// </summary>
        private void PlayerAnimation()
        {
            SpriteRenderer spriteRenderer = GetComponent<SpriteRenderer>();
            //定時器每隔5幀
            Observable.IntervalFrame(5).Subscribe(_ =>
            {
                currentTexture++;
                if (currentTexture >= textureArray.Length)
                {
                    currentTexture = 0;
                }
                spriteRenderer.sprite = textureArray[currentTexture];
            });
        }

        /// <summary>
        /// 鼠標控制移動,每幀更新
        /// </summary>
        private void PlayerMove()
        {
            Observable.EveryUpdate()
                .Subscribe(_ =>
                {
                    //1、獲得當前位置
                    Vector3 curenPosition = this.transform.position;
                    //2、獲得方向
                    if (Input.GetButton('Fire1'))
                    {
                        Vector3 moveToward = Camera.main.ScreenToWorldPoint(Input.mousePosition);

                        moveDirection = moveToward - curenPosition;
                        moveDirection.z = 0;
                        moveDirection.Normalize();
                    }
                    //3、插值移動
                    Vector3 target = moveDirection*speed + curenPosition;
                    transform.position = Vector3.Lerp(curenPosition, target, Time.deltaTime);
                });
        }
    }

    總結

    這里記住UniRx兩個方法 Observable.EveryUpdate,Observable.IntervalFrame(這里還記得以前文章里提的定時器嗎,這個定時器怎么樣簡單吧),還有ObservableWWW.GetWWW(上一篇的一個異步加載資源的函數),采用聲明式編程的方式,看看函數名就知道是干什么的了吧,還用看文檔或者解釋什么嗎?

    文章內容比較簡單,實現的功能也簡單,函數也簡單,希望你們喜歡。

延伸閱讀:

About IT165 - 廣告服務 - 隱私聲明 - 版權申明 - 免責條款 - 網站地圖 - 網友投稿 - 聯系方式
本站內容來自于互聯網,僅供用于網絡技術學習,學習中請遵循相關法律法規
彩票联盟网站大方县| 武乡县|