接上面的(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);
var playerFiring = Rx.Observable
.merge(
Rx.Observable.fromEvent(canvas, 'click'),
Rx.Observable.fromEvent(canvas, 'keydown')
.filter(function(evt) { return evt.keycode === 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) .timestamp();
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;
}, []);
var SHOOTING_SPEED = 15;
function paintHeroShots(heroShots) {
heroShots.forEach(function(shot) {
shot.y -= SHOOTING_SPEED;
drawTriangle(shot.x, shot.y, 5, '#ffff00', 'up');
});
}
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);
function renderScene(actors) {
paintStars(actors.stars);
paintSpaceShip(actors.spaceship.x, actors.spaceship.y);
paintEnemies(actors.enemies);
➤ paintHeroShots(actors.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;
}, []);
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);
}, []);
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');
➤ });
});
}
现在,每个人都在射击,但是没人能被摧毁。它们简单的划过敌人和我们的飞船因为当射击碰到飞船时,我们并没有定义什么。
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);
}
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');
});
}
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);
➤ });
}, []);
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);
});
});
}
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);
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));
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');
});
}
function renderScene(actors) {
paintStars(actors.stars);
paintSpaceShip(actors.spaceship.x, actors.spaceship.y);
paintEnemies(actors.enemies);
paintHeroShots(actors.heroShots, actors.enemies);
➤ paintScore(actors.score);
}