• 技术分享: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官方博客」,转载请注明来源。

  • 相关阅读:
    一步一步使用Ext JS MVC与Asp.Net MVC 3开发简单的CMS后台管理系统之创建Viewport(1)
    CMS之图片管理(1)
    如何将简单CMS后台管理系统示例转换为Java、Php等不同后台语言的版本
    软件开发,维护与支持的困惑
    5 个常用的软件质量指标
    一步一步使用Ext JS MVC与Asp.Net MVC 3开发简单的CMS后台管理系统之创建Viewport(2)
    一步一步使用Ext JS MVC与Asp.Net MVC 3开发简单的CMS后台管理系统之用户管理(2)
    一步一步使用Ext JS MVC与Asp.Net MVC 3开发简单的CMS后台管理系统之用户管理(4)
    JAVA实现DES加密
    找出占用大量资源的SQL
  • 原文地址:https://www.cnblogs.com/worktile/p/10255454.html
Copyright © 2020-2023  润新知