系列目錄
【Unity3D基礎】讓物體動起來①--基於UGUI的鼠標點擊移動
【Unity3D基礎】讓物體動起來②--UGUI鼠標點擊逐幀移動
時光煮雨 Unity3D讓物體動起來③—UGUI DoTween&Unity Native2D實現
時光煮雨 Unity3D實現2D人物動畫① UGUI&Native2D序列幀動畫
時光煮雨 Unity3D實現2D人物動畫② Unity2D 動畫系統&資源效率
背景
前有慕容小匹夫的一篇《解構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所擅長的。
實現
本文以系列文章中的精靈鼠標移動和序列幀動畫為基礎,沒有基礎的先參考下傳統實現方式一下兩篇文章
時光煮雨 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(上一篇的一個異步加載資源的函數),采用聲明式編程的方式,看看函數名就知道是干什么的了吧,還用看文檔或者解釋什么嗎?
文章內容比較簡單,實現的功能也簡單,函數也簡單,希望你們喜歡。