接上面的(5)
- Avoid Drinking from the Firehose
- 这儿存在一个问题,就是接受数据太快了。大部分时间我们需要我们能得到的速度,但是依赖于那些Observable流值非常频繁的,我们可能想丢掉一些我们收到的值。现在我们就是这种场景。我们屏幕重绘的速度和Observable的速度应该是成比例的。但结果是我们最快的Observable对我们来说太快了,我们需要在游戏中创建一个持续更新的速度。
- sample是一个Observable的实例方法,它需要一个单位毫秒的time参数,并返回Observable,这个Observable发射最后一个由父Observable在每个time参数内interval的值。

- 注意sample是如何丢掉在interval的最后一个值之前的所有值的。这是很重要的当你考虑是否需要这种行为时。实际上我们不关心丢掉的那些值,因为我们仅仅是想重绘每个40毫秒中每个元素现在的状态。如果所所有的值都对你很重要,需要考虑buffer操作符。

Rx.Observable.combineLatest(
StarStream, SpaceShip, Enemies,
function(stars, spaceship, enemies) {
return {
stars: stars,
spaceship: spaceship,
enemies: enemies
};
})
➤ .sample(SPEED)
.subscribe(renderScene);
  • 在combineLatest之后调用sample我们确保combineLatest在某一值之后不会稍后的40毫秒内产生任何值。(我们的常量SPEED是40)。
  • Shooting
  • 看到一大波敌人朝我们过来是很可怕的;我们所能做的就是移动并希望他们考到我们。我们怎样赋予我们的英雄射击敌人飞船的能力。
  • 我们希望我们的飞船可以射击无论何时我们点击鼠标或者按空格键。我们将给每个事件创建一个Observable并把它们合并到一个单个的叫做playerShots的Observable上。注意到我们过滤keydown Observable通过空格键的数值,32:
var playerFiring = Rx.Observable
.merge(
Rx.Observable.fromEvent(canvas, 'click'),
Rx.Observable.fromEvent(canvas, 'keydown')
.filter(function(evt) { return evt.keycode === 32; })
)
  • 现在我们已经知道了sample,我们可以使用它给游戏添加点佐料并限制我们飞船设计的频率。然而,游戏者可以很容易地高速射击并摧毁所有的敌人。我们使得游戏者最多每200毫秒才能进行设计。
var playerFiring = Rx.Observable
.merge(
Rx.Observable.fromEvent(canvas, 'click'),
Rx.Observable.fromEvent(canvas, 'keydown')
.filter(function(evt) { return evt.keycode === 32; })
)
.sample(200)
.timestamp();
  • 我们可以看到增加了一个timestamp操作符,它设置我们Observable发射的每个值的确切时间的时间戳属性。之后,我们将用到它。
  • 最后,从我们飞船射击出来的子弹,我们需要知道在开火瞬间飞船的x坐标。这样我们就能在正确的x坐标渲染那个射击了。设置一个外部变量来保存飞船Observable最后一次发射值包含的x坐标是一种尝试,但是这破坏了我们没有变化的外部状态的口头协议。
  • 实际上,通过再次使用我们的好朋友combineLatest,我们将完成这些:
var HeroShots = Rx.Observable
.combineLatest(
playerFiring,
SpaceShip,
function(shotEvents, spaceShip) {return { x: spaceShip.x };
})
.scan(function(shotArray, shot) {shotArray.push({x: shot.x, y: HERO_Y});
return shotArray;
}, []);
  • 由于从SpaceShip和playerFiring我们获取更新值,所以我们可以得到我们想要的x坐标。就像在Enemy Observable中一样,我们使用scan,创建一个我们每次射击的的当前坐标的数组。这样,我么就要准备开始在屏幕上绘制我们的射击了。我们使用一个帮助函数来绘制数组中的每一次射击:
var SHOOTING_SPEED = 15;
function paintHeroShots(heroShots) {heroShots.forEach(function(shot) {shot.y -= SHOOTING_SPEED;
drawTriangle(shot.x, shot.y, 5, '#ffff00', 'up');
});
}
  • 之后在我们主要的combineLatest操作符中调用paintHeroShots。
Rx.Observable.combineLatest(
StarStream, SpaceShip, Enemies, HeroShots,
function(stars, spaceship, enemies, heroShots) {
return {
stars: stars,
spaceship: spaceship,
enemies: enemies,
➤ heroShots: heroShots
};
})
.sample(SPEED)
.subscribe(renderScene);
  • 在renderScene内部,我们调用paintHeroShots:
function renderScene(actors) {
paintStars(actors.stars);
paintSpaceShip(actors.spaceship.x, actors.spaceship.y);
paintEnemies(actors.enemies);
➤ paintHeroShots(actors.heroShots);
}
  • 现在,当你运行这个游戏的时候,每次移动鼠标你会注意到,我们的飞船发射子弹了。一个还不错的结果,但是这还不是我们想要的。让我们重新看下HeroShots Observable。在其中,我们使用combineLatest来获取来自playerFiring和SpaceShip的值。这看起来和我们之前的问题有点相似。每次鼠标移动HeroShots中的combineLatest就会发射值,这导致射击也被触发了。限制在这种情况下不会有帮助,我们想让使用者在他想射击的时候发射,限制会限制射击的次数并漏掉一些。
  • 当一个Observable发射了一个新的值,combineLatest发射每个Observable发射的最后一个值。我们可以利用这个优势。每当鼠标移动,combineLatest发射最新的SpaceShip位置和最后playerFiring发设置,这将不会改变除非我们新开火了。仅仅当射击提交的值与之前的值不同时我们才发射一个值。distinctUntilChanged操作符将会我们做这些复杂的活。
  • distinct和distinctUntilChanged允许我们过滤出一个Observable已发射值中我们想要的结果。distinct过滤出之前已经发射的任何结果,distinctUntilChanged完全一样的除非一个不同的值发射了。我们仅仅需要确认,新的射击和之前的不一样,因此distinctUntilChanged对我们来说足够了。(它也给我们节省了使用distinct的高内存;distinct需要在内存中保存之前所有的值)
  • 我们调整heroShots到它基于他们的时间戳仅能发射新值。
var HeroShots = Rx.Observable
.combineLatest(
playerFiring,
SpaceShip,
function(shotEvents, spaceShip) {return {
timestamp: shotEvents.timestamp,
x: spaceShip.x
};
})
.distinctUntilChanged(function(shot) { return shot.timestamp; })
.scan(function(shotArray, shot) {shotArray.push({ x:shot.x, y: HERO_Y });
return shotArray;
}, []);
  • 如果一切正常,我们将能在我们的飞船上射击敌人!
  • Enemy Shots
  • 我们因该允许敌人也能射击,不然这就是一个极不公平的宇宙了。这很麻烦!对于敌人的射击,我们要如下设计:
  • 1:每个敌人都要维护一个它们自己射击的更新数组。
  • 2:每个敌人都需要在给定的频率下射击。
  • 对于这些,我们将使用interval操作符来存储敌人新射击的值。我们也来介绍一个新的帮助函数,isVisible,它帮助过滤出每一个坐标在可见屏幕之外的元素。现在这就是Enemy Observable看起来:
function isVisible(obj) {
return obj.x > -40 && obj.x < canvas.width + 40 &&
obj.y > -40 && obj.y < canvas.height + 40;
}
var ENEMY_FREQ = 1500;
var ENEMY_SHOOTING_FREQ = 750;
var Enemies = Rx.Observable.interval(ENEMY_FREQ)
.scan(function(enemyArray) {
var enemy = {
x: parseInt(Math.random() * canvas.width),
y: -30,
shots: []
};
Rx.Observable.interval(ENEMY_SHOOTING_FREQ).subscribe(function() {
enemy.shots.push({ x: enemy.x, y: enemy.y });
enemy.shots = enemy.shots.filter(isVisible);
});
enemyArray.push(enemy);
return enemyArray.filter(isVisible);
}, []);
  • 在上面的代码中,每次我们创建一个新的敌人时,我们就创建了一个interval。这个interval将会保持这个添加射击到敌人的射击数组中,之后它过滤掉屏幕之外的。就像我们在return状态里做的,我们使用isVisible过滤掉屏幕之外的敌人。
  • paintEnemies来更新,以便重绘敌人的射击和更新它们的坐标。之后我们的drawTriangle函数重绘那些射击。
function paintEnemies(enemies) {enemies.forEach(function(enemy) {enemy.y += 5;
enemy.x += getRandomInt(-15, 15);
drawTriangle(enemy.x, enemy.y, 20, '#00ff00', 'down');
➤ enemy.shots.forEach(function(shot) {➤ shot.y += SHOOTING_SPEED;
➤ drawTriangle(shot.x, shot.y, 5, '#00ffff', 'down');
➤ });
});
}

现在,每个人都在射击,但是没人能被摧毁。它们简单的划过敌人和我们的飞船因为当射击碰到飞船时,我们并没有定义什么。

  • Managing Collisions
  • 当击中了一个敌人,我们想让射击和敌人都消失。定义一个帮助函数来侦查两个目标碰撞在一起:
function collision(target1, target2) {
return (target1.x > target2.x - 20 && target1.x < target2.x + 20) &&
(target1.y > target2.y - 20 && target1.y < target2.y + 20);
}
  • 现在让我们来修改paintHeroShots帮助函数,来检查是否每个射击击中一个敌人。当一个击中发生的情况下,我们把敌人的一个isDead设置为true来表示击中,并且把坐标设置到屏幕之外。由于被定义到屏幕之外,这个射击将会被过滤掉。
function paintEnemies(enemies) {enemies.forEach(function(enemy) {enemy.y += 5;
enemy.x += getRandomInt(-15, 15);
➤ if (!enemy.isDead) {
➤ drawTriangle(enemy.x, enemy.y, 20, '#00ff00', 'down');
➤ }
enemy.shots.forEach(function(shot) {shot.y += SHOOTING_SPEED;
drawTriangle(shot.x, shot.y, 5, '#00ffff', 'down');
});
});
}
var SHOOTING_SPEED = 15;
function paintHeroShots(heroShots, enemies) {heroShots.forEach(function(shot, i) {for (var l=0; l<enemies.length; l++) {
var enemy = enemies[l];
➤ if (!enemy.isDead && collision(shot, enemy)) {
➤ enemy.isDead = true;
➤ shot.x = shot.y = -100;
➤ break;
➤ }
}
shot.y -= SHOOTING_SPEED;
drawTriangle(shot.x, shot.y, 5, '#ffff00', 'up');
});
}
  • 现在让我们去掉那些isDead属性为true的敌人。仅仅需要说明的是:我们需要等待这个特定的敌人的所有射击都消失;否则,当我们击中一个敌人并且它所有的射击消失,这会很怪异的。因此我们需要检查它射击的长度并过滤掉那些没有射击遗留的敌人。
var Enemies = Rx.Observable.interval(ENEMY_FREQ)
.scan(function(enemyArray) {
var enemy = {
x: parseInt(Math.random() * canvas.width),
y: -30,
shots: []
};
Rx.Observable.interval(ENEMY_SHOOTING_FREQ).subscribe(function() {
➤ if (!enemy.isDead) {
➤ enemy.shots.push({ x: enemy.x, y: enemy.y });
➤ }
enemy.shots = enemy.shots.filter(isVisible);
});
enemyArray.push(enemy);
return enemyArray
.filter(isVisible)
➤ .filter(function(enemy) {
➤ return !(enemy.isDead && enemy.shots.length === 0);
➤ });
}, []);
  • 检查是否游戏者的飞船被击中,我们创建一个gameOver函数:
function gameOver(ship, enemies) {return enemies.some(function(enemy) {if (collision(ship, enemy)) {
return true;
}
return enemy.shots.some(function(shot) {return collision(ship, shot);
});
});
}
  • 如果冲敌人出来的射击击中了游戏者的飞船,这个函数返回true。
  • 在移动之前,让我们来认知一个非常有用的操作符:takeWhile。当在一个存在的Observable上调用takewhile时,这个Observable将会一直发射值直到作为takewhile参数的函数返回一个false。
  • 我们可以使用takeWhile来告诉我们主要的combineLatest Observable来保存传递值直到gameOver返回true:
Rx.Observable.combineLatest(
StarStream, SpaceShip, Enemies, HeroShots,
function(stars, spaceship, enemies, heroShots) {
return {
stars: stars,
spaceship: spaceship,
enemies: enemies,
heroShots: heroShots
};
})
.sample(SPEED)
➤ .takeWhile(function(actors) {
➤ return gameOver(actors.spaceship, actors.enemies) === false;
➤ })
.subscribe(renderScene);
  • 当gameOver返回true,combineLatest将会停止发射值,实际上也停止了这个游戏。
  • One Last Thing: Keeping Score
  • 如果我们不把结果告诉朋友,这种游戏将会咋样?我们需要保存我们玩的记录。我们需要一个分数。
  • 让我们用一个简单的帮助函数,在屏幕的左上角画出这个分数:
function paintScore(score) {ctx.fillStyle = '#ffffff';
ctx.font = 'bold 26px sans-serif';
ctx.fillText('Score: ' + score, 40, 43);
}

为了保存这个分数我们将使用一个Subject。我们可以简单地在我们基于combineLatest游戏的环中使用它,把它当做另外一个Observable,这样我们在我们需要时候保存值。

var ScoreSubject = new Rx.Subject();
var score = ScoreSubject.scan(function (prev, cur) {return prev + cur;
}, 0).concat(Rx.Observable.return(0));
  • 在上面的代码中,我们使用scan 操作符来累加每一新值到最后的聚合结果中。当游戏开始的时候我们没有分数,所以连接一个返回0的Observable作为我们的开始。
  • 现在每当我们击中了某个敌人时我们就在我们的Subject中保存一个分数;paintHeroShots的改变如下:
var SCORE_INCREASE = 10;
function paintHeroShots(heroShots, enemies) {heroShots.forEach(function(shot, i) {for (var l=0; l<enemies.length; l++) {
var enemy = enemies[l];
if (!enemy.isDead && collision(shot, enemy)) {
➤ ScoreSubject.onNext(SCORE_INCREASE);
enemy.isDead = true;
shot.x = shot.y = -100;
break;
}
}
shot.y -= SHOOTING_SPEED;
drawTriangle(shot.x, shot.y, 5, '#ffff00', 'up');
});
}
  • 当然,我们添加一个paintScore到renderScene中以便在屏幕上出现这个分数:
function renderScene(actors) {
paintStars(actors.stars);
paintSpaceShip(actors.spaceship.x, actors.spaceship.y);
paintEnemies(actors.enemies);
paintHeroShots(actors.heroShots, actors.enemies);
➤ paintScore(actors.score);
}
  • 上面完全完成了我们的飞船交互游戏。在两百行代码中,我们成功的设计了一个浏览器的完整游戏,通过强有力的Observable pipeline避免了任何的外部的状态。
  • Ideas for Improvements
  • 我可以肯定你已经有了一些制作这个游戏的更有趣的想法,但是让我提一些可以让这个游戏好的的建议,并同时使你的RxJs技能更犀利:
  • 1:增加一个第二(或者第三)星域,它以一个不同的速度移动来创建视觉差的影响。这可以用击中不同的方式解决。尝试着使用已经存在的代码并使用它像你的格言一样。
  • 2:确保不可预测的敌人,它们开火在一个随机的intervals而不是固定的指定 ENEMY_SHOOTING_FREQ速度。而为指出,当游戏者得分越高,它们开火越快。
  • 3:允许游戏者获取跟多分,当其中在短期内击中若干敌人的时候。

RxJS入门(6)----编写并发程序相关推荐

  1. Fork and Join: Java也可以轻松地编写并发程序 原文地址 作者:Julien Ponge 译者:iDestiny 资源下载: Java SE 7 Sample Code(Zi

    Fork and Join: Java也可以轻松地编写并发程序 原文地址   作者:Julien Ponge 译者:iDestiny 资源下载: Java SE 7 Sample Code(Zip) ...

  2. ubuntu系统下c语言入门以及编写简单程序

    目录 一,hello world入门 二,在windows系统中编写简单程序 三,在ubuntu系统下用makefile方式编写程序 一,hello world入门 在linux操作系统中打开终端 1 ...

  3. ubuntu系统下c语言helloworld入门以及编写简单程序

    文章目录 一.c语言helloworld入门 二.分别在ubuntu和windows系统下编写简单程序 (一)ubuntu系统下的简单主/子程序 (二)在windows系统下编写简单主/子程序 (三) ...

  4. Java8函数式编程_9--使用Lambda表达式编写并发程序

    1,免责声明,本文大部分内容摘自<Java8函数式编程>.在这本书的基础上,根据自己的理解和网上一些博文,精简或者修改.本次分享的内容,只用于技术分享,不作为任何商业用途.当然这本书是非常 ...

  5. 信不信?以面向对象的思想是可以写好高并发程序的!

    来自:冰河技术 前言 面向对象思想与并发编程有关系吗?本来二者是没有什么鸟关系的!它们是分属两个不同的领域,但是,Java却将二者融合在一起了!而且融合的效果不错:我们利用Java的面向对象的思想能够 ...

  6. 理解 C++ 的 Memory Order 以及 atomic 与并发程序的关系

    为什么需要 Memory Order 如果不使用任何同步机制(例如 mutex 或 atomic),在多线程中读写同一个变量,那么,程序的结果是难以预料的.简单来说,编译器以及 CPU 的一些行为,会 ...

  7. 面向对象思想写好并发程序

    面向对象思想写好并发程序 前言 一.封装共享变量 二.识别共享变量间的约束条件 三.制定并发访问策略 总结 前言 在工作中很多时候在设计之初都是直接按照单线程的思路来写程序的,而忽略了本应该重视的并发 ...

  8. java cutdown_Java并发程序入门介绍

    今天看了看Java并发程序,写一写入门程序,并设置了线程的优先级. class Elem implements Runnable{ public static int id = 0; private ...

  9. 明解C语言入门篇_第8章_动手编写各种程序吧

    前言 本文为业余学习<明解C语言入门篇>的记录,包含代码清单和练习题. 开始学习时间:2022年8月21日 +++++++++++++++++++++++++++++++ 第1章 初识C语 ...

最新文章

  1. 3. Swift 数组|字典|集合
  2. android 计算运动速度,android – 计算参考真北的加速度
  3. Windbg dump分析 学习总结
  4. 小波变换学习~语音端点检测
  5. TLD(Tracking-Learning-Detection)学习与源码理解之(三)
  6. 8086汇编-实验4-[bx]和loop的使用
  7. Spring Boot返回前端Long型丢失精度
  8. 改变Fragment的默认动画
  9. 2019 年 8 月编程语言排行榜,C#重回增长之路
  10. javafx css_JavaFX缺少的功能调查:CSS
  11. 微信小程序项目笔记以及openId体验版获取问题
  12. 普通人如何月入10万
  13. 基于yaf+yar微服务解决方案教程
  14. 用EasyRecovery“监控硬盘”功能检测硬盘问题的方法
  15. 敏捷团队章程的实践精要
  16. Android开发——电话簿
  17. Peeking inside LuaJIT(窥探LuaJIT)
  18. Java WEB练习
  19. php++ui设计课程,UI设计主要学什么?
  20. Bean、BeanDefinition、BeanFactory、FactoryBean

热门文章

  1. 数字逻辑与数字电路指导(课后题)
  2. 《高频电子线路》课程教学大纲
  3. 2022年湖北黄冈建设厅七大员(建筑八大员)报名考试时间是什么时候呢?甘建二
  4. 51单片机之蜂鸣器与LED流水灯的简单结合
  5. pandas_day03
  6. 计算机类女生的工作总结,女生工作汇报(共10篇) .docx
  7. 计算机版的游戏怎么玩,《神武4》端游版纯新人前期快速上手攻略
  8. 论“劲舞团”与青少年健康
  9. 7-33 地下迷宫探索(30 分)
  10. pdf转格式怎么转换?其实很简单,看这里就会!