使用Bacon.js构建吃豆子游戏

JavaScript包含异步编程。 这可能是带来“回调地狱”概念的祝福和诅咒。 有一些实用程序库可以处理组织异步代码,例如Async.js ,但是仍然很难有效地遵循控制流程和有关异步代码的原因。

在本文中,我将向您介绍反应性编程的概念,该反应性通过使用称为Bacon.js的库来帮助处理JavaScript的异步特性。

让我们活跃起来

反应式编程是关于异步数据流的。 它将Iterator Pattern替换为Observable Pattern。 这与命令式编程不同,在命令式编程中,您主动遍历数据以处理内容。 在反应式编程中,您订阅数据并异步响应事件。

Bart De Smet在这次演讲中解释了这一转变。 AndréStaltz在本文中深入介绍了反应式编程。

一旦您变得反应灵敏,一切都会变成异步数据流:服务器上的数据库,鼠标事件,承诺和服务器请求。 这样可以避免所谓的“回调地狱”,并为您提供更好的错误处理。 这种方法的另一个强大功能是能够将流组合在一起,从而为您提供了强大的控制能力和灵活性。 Jafar Husain在这次演讲中解释了这些概念。

Bacon.js是一个反应式编程库,它是RxJS的替代 。 在下一节中,我们将使用Bacon.js来构建著名游戏“吃豆人”的版本。

设置项目

要安装Bacon.js,可以通过在CLI上运行以下命令来使用Bower

$ bower install bacon

安装库之后,就可以开始进行反应了。

PacmanGame API和UnicodeTiles.js

在外观上,我将使用基于文本的系统,这样我就不必处理资产和精灵。 为了避免自己创建自己,我将使用一个很棒的库UnicodeTiles.js

首先,我建立了一个名为PacmanGame的类,该类处理游戏逻辑。 以下是其提供的方法:

  • PacmanGame(parent) :创建一个Pacman游戏对象
  • start() :开始游戏
  • tick() :更新游戏逻辑,渲染游戏
  • spawnGhost(color) :产生一个新的幽灵
  • updateGhosts() :更新游戏中的每个幽灵
  • movePacman(p1V) :沿指定方向移动吃豆人

此外,它还公开了以下回调:

  • onPacmanMove(moveV) :如果存在,则当用户通过按键要求Pacman移动时调用

因此,要使用此API,我们将start游戏,定期调用spawnGhost产生spawnGhost ,侦听onPacmanMove回调,并在发生这种情况时调用movePacman实际移动Pacman。 我们还定期调用updateGhosts来更新幻影运动。 最后,我们定期调用tick来更新更改。 重要的是,我们将使用Bacon.js帮助我们处理事件。

在开始之前,让我们创建游戏对象:

var game = new PacmanGame(parentDiv);

我们创建一个新的PacmanGame并传递一个父DOM对象parentDiv并将游戏渲染到该对象中。 现在我们准备好构建我们的游戏了。

EventStreams或Observables

事件流是可观察的,您可以订阅该事件流以异步观察事件。 使用这三种方法可以观察到三种类型的事件:

创建流

既然我们已经了解了事件流的基本用法,让我们看看如何创建事件流。 Bacon.js提供了几种方法 ,可用于从jQuery事件,Ajax承诺,DOM EventTarget,简单回调甚至数组创建事件流。

关于事件流的另一个有用的概念是时间的概念。 也就是说,事件可能会在将来的某个时间发生。 例如,这些方法创建事件流,这些事件流在某个时间间隔传递事件:

为了获得更多控制,您可以使用Bacon.fromBinder()滚动自己的事件流。 我们将通过创建一个moveStream变量在游戏中展示这一点,该变量为我们的Pacman动作生成事件。

var moveStream = Bacon.fromBinder(function(sink) {
   game.onPacmanMove = function(moveV) {
      sink(moveV);
   };
});

我们可以使用将发送事件的值调用接收sink ,观察者可以监听该值。 sink的调用在我们的onPacmanMove回调中–也就是说,只要用户按下某个键来请求Pacman移动,便会发生。 因此,我们创建了一个可观察对象,该事件发出有关Pacman移动请求的事件。

注意,我们用一个简单的值moveV调用了接收sink 。 这将推入带有moveV值的移动事件。 我们还可以推送诸如Bacon.ErrorBacon.End类的事件。

让我们创建另一个事件流。 这次,我们要发出通知以生成幻影的事件。 我们将spawnStream创建一个spawnStream变量:

var spawnStream = Bacon.sequentially(800, [
   PacmanGame.GhostColors.ORANGE,
   PacmanGame.GhostColors.BLUE,
   PacmanGame.GhostColors.GREEN,
   PacmanGame.GhostColors.PURPLE,
   PacmanGame.GhostColors.WHITE,
]).delay(2500);

Bacon.sequentially()创建一个以给定间隔传递values的流。 在我们的案例中,它将每800毫秒产生一次幻影颜色。 我们还调用了delay()方法。 它延迟了流,因此事件将在2.5秒的延迟后开始发出。

事件流和大理石图上的方法

在本节中,我将列出一些可用于事件流的其他有用方法:

有关事件流的更多方法,请参见官方文档页面throttledebounce之间的区别可以通过大理石图看出:

// `source` is an event stream.
//
var throttled = source.throttle(2);

// source:    asdf----asdf----
// throttled: --s--f----s--f--

var debounced = source.debounce(2);

// source:             asdf----asdf----
// source.debounce(2): -----f-------f--

如您所见, throttle像往常一样节流事件,而debounce仅在给定的“安静时间”之后才发出事件。

这些实用程序方法简单但功能强大,能够概念化并控制流,从而控制其中的数据。 我建议观看有关Netflix如何利用这些简单方法创建自动填充框的演讲

观察事件流

到目前为止,我们已经创建并处理了事件流,现在我们将通过订阅事件流来观察事件。

回忆一下我们之前创建的moveStreamspawnStream 。 现在让我们同时订阅它们:

moveStream.onValue(function(moveV) {
   game.movePacman(moveV);
});

spawnStream.onValue(function(ghost) {
   game.spawnGhost(ghost);
});

尽管可以使用stream.subscribe()来订阅流,但是也可以使用stream.onValue() 。 不同的是, subscribe将同时发射三种类型,我们以前见过的事件,而onValue只会发出那些类型的事件Bacon.Next 。 那就是它将省略Bacon.ErrorBacon.End事件。

当事件到达spawnStream (每800毫秒发生一次),其值将是幻影颜色之一,我们使用该颜色生成幻影。 当事件到达moveStream ,请记住,这是在用户按下键以移动Pacman时发生的。 我们将game.movePacman的方向moveVmoveV ,因此Pacman会移动。

结合事件流和Bacon.Bus

您可以组合事件流以创建其他流。 组合事件流的方法有很多,以下是其中几种:

我们来看一个Bacon.combineTemplate的示例:

var password, username, firstname, lastname; // <- event streams
var loginInfo = Bacon.combineTemplate({
   magicNumber: 3,
   userid: username,
   passwd: password,
   name: { first: firstname, last: lastname }
});

如您所见,我们使用模板将事件流(即passwordusernamefirstnamelastname组合为名为loginInfo的组合事件流。 每当事件流获得事件时, loginInfo流将发出事件,将所有其他模板组合为一个模板对象。

Bacon.js还有另一种组合流的方式,即Bacon.Bus()Bacon.Bus()是事件流,允许您将值推送到流中。 它还允许将其他流插入总线。 我们将使用它来构建游戏的最后一部分:

var ghostStream = Bacon.interval(1000, 0);

ghostStream.subscribe(function() {
   game.updateGhosts();
});

var combinedTickStream = new Bacon.Bus();

combinedTickStream.plug(moveStream);
combinedTickStream.plug(ghostStream);

combinedTickStream.subscribe(function() {
   game.tick();
});

现在我们创建另一个流-在ghostStream ,使用Bacon.interval 。 该流将每1秒发射0。 这次我们subscribe它并调用game.updateGhosts来移动幽灵。 这是每1秒移动一次幽灵。 注意注释掉game.tick ,并记住其他game.tick我们moveStream ? 这两个流都更新了游戏,最后调用game.tick来呈现更改,因此,我们可以在每个流中生成第三流(这两个流的组合),而不是在每个流中调用game.tick ,并在合并的流中调用game.tick流。

为了合并流,我们可以使用Bacon.Bus 。 那就是我们游戏中的最终事件流,我们称其为combinedTickStream 。 然后我们plug moveStreamghostStream插入其中,最后subscribe它并在其中调用game.tick

就是这样,我们完成了。 剩下要做的就是用game.start();开始游戏game.start();

Bacon.Property及更多示例

Bacon.Property是一种反应性。 考虑一个反应性属性,它是一个数组的总和。 当我们向数组添加元素时,反应性属性将做出反应并进行自我更新。 要使用Bacon.Property ,您可以订阅它并侦听更改,也可以使用property.assign(obj,method)方法,该method在属性更改时调用给定objectmethod 。 这是一个如何使用Bacon.Property

var source = Bacon.sequentially(1000, [1, 2, 3, 4]);

var reactiveValue = source.scan(0, function(a, b) {
   return a + b;
});

// 0 + 1 = 1
// 1 + 2 = 3
// 3 + 3 = 6
// 6 + 4 = 10

首先,我们创建一个事件流,该事件流以1秒的间隔产生给定数组(1、2、3和4)的值,然后创建一个响应性属性,该属性是scan的结果。 这将分配为1,3,6,和10个值reactiveValue

了解更多和现场演示

在本文中,我们通过构建Pacman游戏介绍了Bacon.js的反应式编程。 它简化了我们的游戏设计,并通过事件流的概念为我们提供了更多的控制和灵活性。 完整的源代码可在GitHub上找到 ,并在此处提供实时演示。

以下是一些更有用的链接:

From: https://www.sitepoint.com/building-pacman-with-bacon-js/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值