zoukankan      html  css  js  c++  java
  • 技术分享:RxJS实战练习-经典游戏Breakout

    效果图

    数据流分析

    1.ticker$ 数据流 interval配合scheduler/animationFrame 作为游戏随时间变化的控制数据流

    ticker$ = interval(this.TICKER_INTERVAL, animationFrame).pipe(
    map(() => ({
    time: Date.now(),
    deltaTime: null
    })),
    scan((previous, current) => ({
    time: current.time,
    deltaTime: (current.time - previous.time) / 1000
    }))
    ); // Observable单播 每次订阅都是启动一个数据流

    2.key$ 数据流检测keydown/keyup 玩家控制的部分(整个状态中的一个副作用),改变底部船桨的位置

    PADDLE_CONTROLS = {
    ArrowLeft: -1,
    ArrowRight: 1
    };
    key$ = merge(
    fromEvent(document, 'keydown').pipe(
    map(event => this.PADDLE_CONTROLS[event['key']] || 0)
    ),
    fromEvent(document, 'keyup').pipe(map(event => 0))
    ).pipe(distinctUntilChanged()); // 提供船桨移动的方位的数据源

    实现逻辑:按下‘<’直到 keyup 输出 -1 / 按下‘>’直到 keyup 输出 1 / keyup 输出 0 3.paddle$ 数据流使用操作符withLatestFrom合并了ticker$和key$ 持续流出船桨的位置

    createPaddle$(ticker$: Observable<{ time: number; deltaTime: any }>) {
    return ticker$.pipe(
    withLatestFrom(this.key$), // withLatestFrom操作符 作为游戏开始的触发条件,只有这个数据流产生数据才会往下游流动
    scan<[{ deltaTime: number; time: number }, number], number>(
    (position: number, [ticker, direction]) => {
    const nextPosition =
    position + direction * ticker.deltaTime * this.PADDLE_SPEED;
    return Math.max(
    Math.min(
    nextPosition,
    this.breakoutCanvasService.stage.width - config.PADDLE_WIDTH / 2
    ),
    config.PADDLE_WIDTH / 2
    );
    },
    this.breakoutCanvasService.stage.width / 2
    ),
    distinctUntilChanged()
    );
    }

    3.createState$ 数据流使用withLatestFrom合并ticker$和paddle$ 最终输出界面需要的全部状态数据

    createState$(ticker$, paddle$) {
    return ticker$.pipe(
    withLatestFrom(paddle$),
    scan<
    [{ deltaTime: number; time: number }, number],
    { ball: Ball; bricks: Brick[]; score: number }
    >(({ ball, bricks, score }, [ticker, paddle]) => {
    const remainingBricks = [];
    const collisions = {
    paddle: false, // 球撞船桨
    floor: false, //
    wall: false, // 撞墙
    ceiling: false, // 撞顶
    brick: false // 球撞砖块
    };
    ball.position.x =
    ball.position.x +
    ball.direction.x * ticker.deltaTime * this.BALL_SPEED;
    ball.position.y =
    ball.position.y +
    ball.direction.y * ticker.deltaTime * this.BALL_SPEED;
    bricks.forEach(brick => {
    if (!this.isCollision(brick, ball)) {
    remainingBricks.push(brick);
    } else {
    collisions.brick = true;
    score = score + 10;
    }
    });
    collisions.paddle = this.isHit(paddle, ball);
    if (
    ball.position.x < config.BALL_RADIUS ||
    ball.position.x >
    this.breakoutCanvasService.stage.width - config.BALL_RADIUS
    ) {
    ball.direction.x = -ball.direction.x;
    collisions.wall = true;
    }
    
    collisions.ceiling = ball.position.y < config.BALL_RADIUS;
    if (collisions.brick || collisions.paddle || collisions.ceiling) {
    if (collisions.paddle) {
    ball.direction.y = -Math.abs(ball.direction.y);
    } else {
    ball.direction.y = -ball.direction.y;
    }
    }
    
    return {
    ball: ball,
    bricks: remainingBricks,
    collisions: collisions,
    score: score
    };
    }, this.initState())
    );
    }
    • 用到ticker$流控制球的移动位置
    • 根据当前状态控制下一步的状态,包括计分、球的运动方向、砖块数量

    4.game$ 数据流最终的游戏控状态输出流(包括这状态数据、船桨位置数据)

    game$ = Observable.create(observer => {
    this.breakoutCanvasService.drawIntro();
    this.restart = new Subject();
    const paddle$ = this.createPaddle$(this.ticker$); // 数据源吐出船桨的位置
    const state$ = this.createState$(this.ticker$, paddle$);
    this.ticker$
    .pipe(
    withLatestFrom(paddle$, state$),
    OperatorMerge(this.restart)
    )
    .subscribe(observer); // 这个this.ticker$ 也可以不使用,直接通过merge合并后面两个数据流
    });

    merge数据流restart$后 可以通过error方法终止流从而控制游戏结束

    状态

    两个结果状态:砖块数量、分数

    两个影响状态的副作用:时间、游戏者的行为

    状态交叉点

    球接触砖块 -> 砖块消失

    球接触船桨/墙 -> 球自然改变运动方向

    整个过程用rxjs实现不需要额外保存中间数据,在管道中实现数据的缓存、状态处理 。

    两个字形容 “优秀”

    演示地址:http://tiny.pubuzhixing.com/

    github:https://github.com/pubuzhixing8/tiny-game 

    出处:《深入浅出RxJS》十四章实例,使用TS+Angular重新包装,修改了一个小缺陷,据说这个游戏最初是由乔布斯和他的一个朋友设计


    Worktile官网:www.worktile.com 

    本文作者:徐海峰

    文章首发于「Worktile官方博客」,转载请注明来源。

  • 相关阅读:
    后缀数组简要总结
    2019CCPC网络赛
    2019 Multi-University Training Contest 6
    洛谷P4145——上帝造题的七分钟2 / 花神游历各国
    扫描线——POJ1151
    2012Noip提高组Day2 T3 疫情控制
    2012Noip提高组Day1 T3 开车旅行
    JZOJ.5335【NOIP2017模拟8.24】早苗
    三套函数实现应用层做文件监控
    LLVM一个简单的Pass
  • 原文地址:https://www.cnblogs.com/worktile/p/10255454.html
Copyright © 2011-2022 走看看