[譯] ReactiveX 與 Unity3D <二.1>

原文鏈接

這是三部曲的第二篇盗温。使用 ReactiveX 實(shí)現(xiàn) Unity 標(biāo)準(zhǔn)資源包中的第一人稱控制器,是這個系列文章的主要內(nèi)容婶恼。

我們上一篇已經(jīng)完成了行走和鼠標(biāo)控制視野功能「坑牵現(xiàn)在我們來添加奔跑功能和攝像機(jī)震動功能痕钢。這篇文章里我們將會開始看到一些我們在第一部分所做工作的一些回報(bào)。以下是我們這次將要完成的效果:

<iframe width="786" height="442" src="https://www.youtube.com/embed/G_N8l9Sd-aI" frameborder="0" allowfullscreen></iframe>

My little runaway

(譯注:題目好像是歌名)

"按住 Shift 跑動" 在第一人稱游戲中幾乎是標(biāo)準(zhǔn)實(shí)踐鱼冀,所以我們要支持這個功能报破。即便使用 Observable 也有很多種實(shí)現(xiàn)方式。不過我覺得這是一個介紹響應(yīng)式屬性(Reactive Properties)的絕佳機(jī)會千绪。這個特性是 UniRx 中獨(dú)有的充易,它不在 ReactiveX 的標(biāo)準(zhǔn)中。

響應(yīng)式屬性讓我們二者兼得:技能擁有屬性的靈活性荸型,也可以擁有 Observable 的功能盹靴。你不僅可以像設(shè)置和獲取一個正常屬性值那樣來操作響應(yīng)式屬性,而且你還可以通過訂閱這個屬性來得到它變化的通知。將奔跑的信號輸入轉(zhuǎn)換成響應(yīng)式屬性的原因是稿静,我不想對用戶按下和釋放按鍵做出反應(yīng)梭冠,我僅僅想要知道在計(jì)算移動時,按鍵處于按下狀態(tài)改备。下面給 Inputs 腳本添加一些代碼(我省略了之前的代碼)

public ReadOnlyReactiveProperty<bool> Run { get; private set; }
// ...

private void Awake() {
  // ...
  Run = this.UpdateAsObservable()
    .Select(_ => Input.GetButton("Fire3"))
    .ToReadOnlyReactiveProperty();
}

首先控漠,我聲明了一個 ReadOnlyReactiveProperty屬性。如果我使用普通的 ReactiveProperty 屬性悬钳,那么任何代碼都可以改變它的值盐捷。為了能讓你的代碼解耦,更好的方式是默勾,在任何可能的情況下限制寫的權(quán)限碉渡。并且無論什么情況下,我們都不必主動設(shè)置 Run 的值母剥,因?yàn)槲覀兺耆梢灾圃炝硪粋€ Observable 來生產(chǎn)新值爆价。正如我們在 Awake 中所做的:每次 Update 時得到 “Fire3” 按鈕的狀態(tài),將它轉(zhuǎn)化為屬性值媳搪。(“Fire3” 是 Unity 項(xiàng)目默認(rèn)定義的輸入軸, 可以方便地匹配到 Shift 鍵)

使用這個輸入也很簡單铭段。在 PlayerController 添加一個 runSpeed 屬性。現(xiàn)在我們在計(jì)算移動的時候秦爆,就可以查詢 Run 的值來決定使用哪種速度了序愚。

// In PlayerController.Start()
inputs.Movement
.Where(v => v != Vector2.zero)
.Subscribe(inputMovement => {
  var inputVelocity = inputMovement * (inputs.Run.Value ? runSpeed : walkSpeed);
    // ... etc.

這可能是實(shí)現(xiàn)這個功能最簡單的方式了,但它是否足夠好呢等限? 此處使用 Observable (或者任何其他的異步代碼) 有些許微妙之處:因?yàn)槌莾蓚€輸入信號直接依賴于彼此爸吮,否則我們基本上無法保證它們的執(zhí)行順序。換句話說:我不清楚當(dāng)我訪問 Run 值的時候望门,它是否已經(jīng)更新完畢了形娇。所以在使用這段代碼的時候,我們心中最好有預(yù)期筹误。

站在移動信號的角度看桐早, Run 值可能會落后一幀。現(xiàn)在有方法可以確保正確的執(zhí)行順序(我們將在第三部分看到他們)厨剪,但這會是你的代碼變得復(fù)雜化哄酝。你要想清楚這樣做是否值得。跑慢一幀有沒有關(guān)系祷膳?可能沒問題陶衅,也可能有。這當(dāng)然就是你的工作啦直晨,去把它找出來搀军。但現(xiàn)在膨俐,我們會繼續(xù)使用這個簡單的方法。

在下面的情況下罩句,你可能對這種執(zhí)行效果感覺足夠了:首先你知道 Update 是在 FixedUpdate 之前處理的吟策,并且我們代碼的調(diào)用都是像這樣直截了當(dāng)?shù)摹5悴荒苤竿@點(diǎn)的止。所以最好是圍繞這個問題進(jìn)行設(shè)計(jì)檩坚。

Bob and un-weave

實(shí)現(xiàn)攝像機(jī)擺動將會帶來更多的代碼混合,也因此我們可以看到 Observables 展現(xiàn)給我們的低耦合代碼了诅福。我們會對攝像機(jī)進(jìn)行輕微的彈動來模擬行走匾委,所以我們需要知道移動時每一幀間隔的空間距離。在標(biāo)準(zhǔn)資源中氓润,這個效果是通過讓播放器的控制器直接更新負(fù)責(zé)相機(jī)擺動效果的類來實(shí)現(xiàn)的赂乐。自然地,這會將播放器控制器代碼和相機(jī)擺動的代碼耦合在一起咖气,而這正是我們要避免的“ご耄現(xiàn)在,我們要使用 Observable 生成這兩個類之間的接口崩溪。(好吧浅役,它實(shí)際上是一個抽象類,但是很容易使用 Unity 的 inspector 做兼容處理)

public abstract class PlayerSignals : MonoBehaviour {
  public abstract float StrideLength { get; }
  public abstract IObservable<Vector3> Walked { get; }
}

我們的 PlayerController 將會繼承并實(shí)現(xiàn)這個抽象類(譯注:原作者在代碼的第一版時使用的是接口伶唯,后又轉(zhuǎn)而使用了抽象類觉既,原文此處行文為接口),因此控制相機(jī)擺動的腳本則不需要直接依賴 PlayerController 乳幸。StrideLength 是一個簡單的配置項(xiàng)瞪讼。那我們怎么實(shí)現(xiàn) Walked Observable 呢? Unity 的 CharacterController 組件實(shí)際上會為我們計(jì)算這個值(這是考慮到墻壁碰撞后移動的實(shí)際距離),我們要做的就是導(dǎo)入這個值粹断。改寫移動的代碼符欠。

inputs.Movement
  .Where(v => v != Vector2.zero)
  .Subscribe(inputMovement => {
    // ...
    var distance = playerVelocity * Time.fixedDeltaTime;
    character.Move(distance);
    var distanceActuallyWalked = character.velocity * Time.fixedDeltaTime;
}).AddTo(this);

我們想要將 distanceActuallyWalked 放到一個 Observable 中。但是我說過不能從外部向 Observable 中注入值瓶埋,對吧希柿?

其中一個選擇是介紹一個新概念:Subject。在 ReactiveX 中悬赏,Subject 結(jié)合了 Observer 和 Observable 狡汉, 但你不能將它理解成是一個 “可讀寫的 Observable”。 那它就與響應(yīng)式屬性沒有什么區(qū)別了闽颇。Subject 沒有像響應(yīng)式屬性那樣定義一個“當(dāng)前值”。 你只能通過訂閱 Subject 來得到它改變后傳給你的通知寄锐,但你不能拿到它的當(dāng)前值兵多。所以你可以將它理解成閹割版的響應(yīng)式屬性尖啡。而且你在任何情況下都應(yīng)該選擇能使代碼正常運(yùn)行而功能添加又最少的選項(xiàng)。在實(shí)踐中需要根據(jù)如何使用信號來進(jìn)行決策剩膘。

那我們在 PlayerController 腳本中添加一個 Subject<Vector3> 字段衅斩,因?yàn)檫@是屬于我們自己的信號(譯注:“我們”指 PlayerController, 相對于 Inputs而言)怠褐,那就需要在 Awake 中初始化它畏梆。

walked = new Subject<Vector3>().AddTo(this);

為了能在我們的游戲?qū)ο笾屑s束這個信號的生命周期,添加了 AddTo(this)奈懒。

現(xiàn)在我們用下面這行代替 distanceActuallyWalked:

walked.OnNext(character.velocity * Time.fixedDeltaTime);

OnNext 方法會為信號提供一個新的值奠涌。任何訂閱這個信號的人都會得到攜帶這個新值得通知。

我們到現(xiàn)在還沒接觸到相機(jī)擺動的那一部分磷杏,但是我覺得我們已經(jīng)做的相當(dāng)不錯了溜畅。我們的腳本不僅僅只是“消耗”信號,而且還能產(chǎn)生新的信號极祸。這使你可以集成不基于 Observable 的系統(tǒng)慈格,比如 Unity 的物理系統(tǒng)和場景圖。

這里最后需要記著點(diǎn)遥金。到目前為止浴捆,我都十分謹(jǐn)慎地限制著代碼的讀寫權(quán)限。如果我公開了 Subject 的權(quán)限稿械,那么潛在地汤功,任何人都可能在此處修改信號的值并破壞我們的代碼(可能是在上線前一天的夜里3點(diǎn)鐘)。不過不用擔(dān)心:因?yàn)?Subject 是一個 Observable溜哮,我們可以像下面這樣通過定義來簡單的限制一下我們變量的可見性滔金。

private Subject<Vector3> walked;
public override IObservable<Vector3> Walked {
  get { return walked;  }
}

我們可以看見 Subject,其他人只能看見 IObservable茂嗓。干凈漂亮餐茵!

Okey,接著就是實(shí)際的相機(jī)擺動腳本了述吸!這個腳本需要設(shè)置在相機(jī)的游戲?qū)ο笊戏拮澹刂扑倪\(yùn)動。我們要訂閱 Walked 信號并累計(jì)玩家移動的距離蝌矛,然后在對步長取模道批。我們用 Unity AnimationCurve 將這個值轉(zhuǎn)化為能正確調(diào)整相機(jī)位置的正弦曲線。(譯注:代碼中的注釋就不翻譯了入撒,最下面有完整的代碼和注釋)

public class CameraBob : MonoBehaviour {
  // IPlayerSignals reference configured in the Unity Inspector, since we can
  // reasonably expect these game objects to be in the same hierarchy
  public PlayerSignals player;
  public float walkBobMagnitude = 0.05f;
  public float runBobMagnitude = 0.10f;

  public AnimationCurve bob;

  private Camera view;
  private Vector3 initialPosition;

  private void Awake() {
    view = GetComponent<Camera>();
    initialPosition = view.transform.localPosition;
  }

  private void Start() {
    var distance = 0f;
    player.Walked.Subscribe(w => {
      // Accumulate distance walked (modulo stride length).
      distance += w.magnitude;
      distance %= player.StrideLength;
      // Use distance to evaluate the bob curve.
      var magnitude = InputsV2.Instance.Run.Value ? runBobMagnitude : walkBobMagnitude;
      var deltaPos = magnitude * bob.Evaluate(distance / player.StrideLength) * Vector3.up;
      // Adjust camera position.
      view.transform.localPosition = initialPosition + deltaPos;
    }).AddTo(this);
  }
}

注意隆豹,distance 變量是需要在訂閱內(nèi)部使用的 狀態(tài)。在這我用使用了閉包來實(shí)現(xiàn)茅逮。之前我們是使用類變量來實(shí)現(xiàn)的璃赡。(例如:CharacterController 實(shí)例)

為了提升真實(shí)感判哥,我們還可以根據(jù)玩家的跑動狀態(tài)來決定相機(jī)擺動的幅度大小。歸根結(jié)底碉考,這個例子是為了說明如何清晰整潔地重用一個信號:當(dāng)我們處理 Run 的時候真的無需考慮相機(jī)如何擺動塌计。

我們完成了第2部分!我們添加了跑動和相機(jī)特效锌仅,而并沒有將我們的代碼混雜。當(dāng)需求變化時墙贱,我們可以輕易地將相機(jī)擺動的效果移除热芹,因?yàn)樗鼪]有和任何代碼耦合在一起。接下來的第三部分嫩痰,我們將會添加跳躍和一些其他的效果剿吻。

你可以在 GitHub Gist 上找到完整的代碼.


譯注:以下是完整代碼,作者貼到了 github 上串纺,沒有寫在原文中

Inputs.cs

using UnityEngine;
using UniRx;
using UniRx.Triggers;
using System;

public class Inputs : MonoBehaviour {
  // 單例
  public static Inputs instance;
    
  public IObservable<Vector2> movement { get; private set; }
  public IObservable<Vector2> mouselook { get; private set; }
  public ReadOnlyReactiveProperty<bool> run { get; private set; }
  public void Awake () {
    instance = this;

    // 隱藏鼠標(biāo)指針丽旅,將其鎖定在游戲窗口內(nèi)
    Cursor.lockState = CursorLockMode.Locked;
    Cursor.visible = false;

    // 移動輸入 tick 基于 fixedUpdate
    movement = this.FixedUpdateAsObservable()
    .Select(_ => {
      var x = Input.GetAxis("Horizontal");
      var y = Input.GetAxis("Vertical");
            
      return new Vector2(x, y).normalized;
    });

    // 鼠標(biāo)視野 tick 基于 Update
    mouselook = this.UpdateAsObservable()
    .Select(_ => {
      var x = Input.GetAxis("Mouse X");
      var y = Input.GetAxis("Mouse Y");

      return new Vector2(x, y);
    });

    // 按下時奔跑
    run = this.UpdateAsObservable()
    .Select(_ => Input.GetButton("Fire3"))
    .ToReadOnlyReactiveProperty();

  }
}

PlayerSignals.cs

using UnityEngine;
using UniRx;

public abstract class PlayerSignals: MonoBehaviour {
  public abstract float strideLength { get; }
  public abstract IObservable<Vector3> walked { get; }
}

PlayerController.cs

using UnityEngine;
using UniRx;

[RequireComponent(typeof(CharacterController))]
public class PlayerController : PlayerSignals {

  float walkSpeed = 5f;
  float runSpeed = 10f;
  float _strideLength = 2.5f;

  [Range(-90, 0)]
  public float minViewAngle = -60; // 玩家最低能看多少角度
    
  [Range(0, 90)]
  public float maxViewAngle = 60; // 玩家最高能看多少角度

  // 實(shí)現(xiàn) PlayerSignal
  public override float strideLength {
    get { return _strideLength; }
  }

  private Subject<Vector3> _walked; // 我們自己看是 Subject

  public override IObservable<Vector3> walked {
    get { return _walked; } // 其他人看是 IObservable
  }

  CharacterController character;
  Camera view;

  void Awake () {
    character = GetComponent<CharacterController>();
    view = GetComponentInChildren<Camera>();

    _walked = new Subject<Vector3>().AddTo(this);
  }
    
  void Start () {
    var inputs = Inputs.instance;

    // 處理 wsad 的行走和奔跑效果
    inputs.movement
    .Where(v2 => v2 != Vector2.zero) // 如果移動量為0則忽略
    .Subscribe(inputMovement => {
            
      // 計(jì)算速度 (方向 * 速率)
      var inputVelocity = inputMovement * (inputs.run.Value ? runSpeed : walkSpeed);
            
      // 將 2D 的速度 轉(zhuǎn)化為 3D 玩家的坐標(biāo)
      var playerVelocity = 
        inputVelocity.x * transform.right +  // x (+/-) 對應(yīng)右/左
        inputVelocity.y * transform.forward; // y (+/-) 對應(yīng)前/后

      // 使用移動量
      var distance = playerVelocity * Time.fixedDeltaTime;
      character.Move(distance);

            
      // 移動產(chǎn)生信號
      _walked.OnNext(character.velocity * Time.fixedDeltaTime);

    }).AddTo(this);

    // 處理鼠標(biāo)輸入
    inputs.mouselook
    .Where(v2 => v2 != Vector2.zero) // 如果鼠標(biāo)沒動則忽略
    .Subscribe(inputLook => {
      // 將 2D 鼠標(biāo)輸入轉(zhuǎn)化為歐拉角的轉(zhuǎn)動量

      // inputLook.x 使角色繞縱軸旋轉(zhuǎn)(+ 代表右轉(zhuǎn))
      var horzLook = inputLook.x * Time.deltaTime * Vector3.up * 100.0f;
      transform.localRotation *= Quaternion.Euler(horzLook);

      // inputLook.y 使相機(jī)繞橫軸旋轉(zhuǎn) (+ 代表向上轉(zhuǎn))
      var verLook = inputLook.y * Time.deltaTime * Vector3.left * 100.0f;
      var newQ = view.transform.localRotation * Quaternion.Euler(verLook);

      // 我們必須在這里翻轉(zhuǎn)最小/最大視角的標(biāo)志和位置, 
      // 因?yàn)榇颂帞?shù)學(xué)計(jì)算和角度相矛盾(+/- 對應(yīng)下/上)
      view.transform.localRotation = ClampRotationAroundXAxis(
        newQ, -maxViewAngle, -minViewAngle
      );
    });
  }

  // 直接從標(biāo)準(zhǔn)資源中的 MouseLook 腳本中拿出來的(這真的是一個標(biāo)準(zhǔn)函數(shù)...)
  private static Quaternion ClampRotationAroundXAxis (
    Quaternion q, float minAngle, float maxAngle
  ) {
    q.x /= q.w;
    q.y /= q.w;
    q.z /= q.w;
    q.w = 1.0f;

    float angleX = 2.0f * Mathf.Rad2Deg * Mathf.Atan(q.x);
    angleX = Mathf.Clamp(angleX, minAngle, maxAngle);

    q.x = Mathf.Tan(0.5f * Mathf.Deg2Rad * angleX);
    return q;
  }
    
}

CameraBob.cs

using UnityEngine;
using UniRx;

[RequireComponent(typeof(Camera))]
public class CameraBob: MonoBehaviour {

  public PlayerSignals player;

  float walkBobMagnitude = 0.05f;
  float runBobMagnitude = 0.10f;

  public AnimationCurve bob = new AnimationCurve(
    new Keyframe(0.00f,  0f),
    new Keyframe(0.25f,  1f),
    new Keyframe(0.50f,  0f),
    new Keyframe(0.75f, -1f),
    new Keyframe(1.00f,  0f)
  );

  Camera view;
  Vector3 initialPosition;

  void Awake () {
    view = GetComponent<Camera>();
    initialPosition = view.transform.localPosition;

    // 譯注: 作者在 Inspector 界面進(jìn)行配置,為了更好理解
    // 將獲取腳本的代碼寫在了這纺棺。但這樣使得代碼變的耦合有利有弊
    player = transform.parent.GetComponent<PlayerSignals>();
  }

  void Start () {
    var distance = 0f;
    player.walked.Subscribe(w => {
      
      // 累計(jì)行走的距離(步幅的模長)
      distance += w.magnitude;
      distance %= player.strideLength;

      // 用 distance 設(shè)置相機(jī)的震動曲線
      var magnitude = Inputs.instance.run.Value ? runBobMagnitude : walkBobMagnitude;
      var deltaPos = magnitude * bob.Evaluate(distance / player.strideLength) * Vector3.up;

      //  調(diào)整相機(jī)位置
      view.transform.localPosition = initialPosition + deltaPos;

    }).AddTo(this);
  }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末榄笙,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子祷蝌,更是在濱河造成了極大的恐慌茅撞,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,997評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件巨朦,死亡現(xiàn)場離奇詭異米丘,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)糊啡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評論 3 392
  • 文/潘曉璐 我一進(jìn)店門拄查,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人棚蓄,你說我怎么就攤上這事堕扶。” “怎么了梭依?”我有些...
    開封第一講書人閱讀 163,359評論 0 353
  • 文/不壞的土叔 我叫張陵稍算,是天一觀的道長。 經(jīng)常有香客問我役拴,道長糊探,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,309評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮侧到,結(jié)果婚禮上勃教,老公的妹妹穿的比我還像新娘淤击。我一直安慰自己匠抗,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,346評論 6 390
  • 文/花漫 我一把揭開白布污抬。 她就那樣靜靜地躺著汞贸,像睡著了一般。 火紅的嫁衣襯著肌膚如雪印机。 梳的紋絲不亂的頭發(fā)上矢腻,一...
    開封第一講書人閱讀 51,258評論 1 300
  • 那天,我揣著相機(jī)與錄音射赛,去河邊找鬼多柑。 笑死,一個胖子當(dāng)著我的面吹牛楣责,可吹牛的內(nèi)容都是我干的竣灌。 我是一名探鬼主播,決...
    沈念sama閱讀 40,122評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼秆麸,長吁一口氣:“原來是場噩夢啊……” “哼初嘹!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起沮趣,我...
    開封第一講書人閱讀 38,970評論 0 275
  • 序言:老撾萬榮一對情侶失蹤屯烦,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后房铭,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體驻龟,經(jīng)...
    沈念sama閱讀 45,403評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,596評論 3 334
  • 正文 我和宋清朗相戀三年缸匪,在試婚紗的時候發(fā)現(xiàn)自己被綠了翁狐。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,769評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡豪嗽,死狀恐怖谴蔑,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情龟梦,我是刑警寧澤隐锭,帶...
    沈念sama閱讀 35,464評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站计贰,受9級特大地震影響钦睡,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜躁倒,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,075評論 3 327
  • 文/蒙蒙 一荞怒、第九天 我趴在偏房一處隱蔽的房頂上張望洒琢。 院中可真熱鬧,春花似錦褐桌、人聲如沸衰抑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,705評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽呛踊。三九已至,卻和暖如春啦撮,著一層夾襖步出監(jiān)牢的瞬間谭网,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,848評論 1 269
  • 我被黑心中介騙來泰國打工赃春, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留愉择,地道東北人。 一個月前我還...
    沈念sama閱讀 47,831評論 2 370
  • 正文 我出身青樓织中,卻偏偏與公主長得像锥涕,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子抠璃,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,678評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 原文鏈接 第一篇 第二篇 第二篇附加篇 第三篇 耦合性強(qiáng)的代碼令人頭痛站楚。一定是有某種自然力量,像重力那樣搏嗡,拽著代碼...
    binyu1231閱讀 1,024評論 0 4
  • 本文章內(nèi)部分圖片資源來自RayWenderlich.com 本文結(jié)合自己的理解來總結(jié)介紹一下RxSwift最基本的...
    FKSky閱讀 2,879評論 4 14
  • 介紹 RxJS是一個異步編程的庫窿春,同時它通過observable序列來實(shí)現(xiàn)基于事件的編程。它提供了一個核心的類型:...
    泓滎閱讀 16,601評論 0 12
  • 原文鏈接 第一篇 第二篇 第二篇附加篇 第三篇 本系列的最后一篇文章采盒。我們將會為玩家控制器添加跳躍功能及其聲音的播...
    binyu1231閱讀 639評論 0 0
  • 響應(yīng)式編程簡介 響應(yīng)式編程是一種基于異步數(shù)據(jù)流概念的編程模式旧乞。數(shù)據(jù)流就像一條河:它可以被觀測,被過濾磅氨,被操作尺栖,或者...
    說碼解字閱讀 3,066評論 0 5