React, TypeScript 写游戏探索

如何用 React, TypeScript 写游戏?

image

1. React的优势

  • 数据驱动, 根据state或者props的变化 => 视图的变化, 以前的方式往往是直接操作 DOM 实现, 触发某事件使得元素移动代码类似如:
=>
    this.moveRight = () => {
        this.left += 8;
        this.draw();
    }

    this.draw = () => {
        if(this.ele === null){
            this.ele = document.createElement('img');
            this.ele.src = this.url;
            this.ele.style.width = this.width + 'px';
            this.ele.style.height = this.height + 'px';
            this.ele.style.position = 'absolute';
            app.appendChild(this.ele);
        }
        this.ele.style.left = this.left + 'px';
        this.ele.style.top = this.top + 'px';
    };

现在就友好很多

=>
    this.moveRight = () => {
        this.setState( preState => (
            {
                left: preState.left + 8
            }
        ));
    }

    <ContraBG
        left={left}
        top={top}
        status={status}
        toward={toward}>
    </ContraBG>
  • 结构更清晰, 逐个书写需要渲染的组件, 能让人一目了然的知道游戏运行中加载的组件, 老的方式代码风格去渲染一个元素如
=>
    const plane = new ourplane();
    plane.draw();

如果渲染的多了结构复杂了,阅读就会十分困难。现在的代码风格就能够一目了然的看到所有运行的组件

=>
    @observer
    class InGame extends React.PureComponent<InGameProps, {}> {
        render() {
            const { store } = this.props;
    
            return (
                <InGameBG   // 包裹组件负责渲染背景变化相关
                        store={store}>
                        <Contra // 玩家控制的角色组件
                            store={store}/>
                        <BulletsMap // 负责渲染子弹
                            store={store}/>
                        <EnemiesMap // 负责渲染敌方角色
                            store={store}/>
                </InGameBG>
            );
        }
    }

2. React的劣势

  • 灵活性
    前者类与类之间继承会灵活很多, 如
    飞机继承至飞行物 => 飞行物继承至动态物 => 动态物继承至某一特性物体

其中子弹也可以继承至飞行物使得飞行物等可以衍生更多子类。React中各组件只能继承至React.Component,可采用HOC高阶组件思想去渲染一系列具有相似性质的组件。如超级玛丽游戏中有许多的墙,它们具有相似的渲染逻辑,以及一些都会需要用到的方法, 可以通过写一个静态方块的高阶组件去生成, 能够更高效的管理代码。

=>
    function WithStaticSquare<TOwnProps>(options: StaticSquareOption):ComponentDecorator<TOwnProps> {
        return Component =>
            class HocSquare extends React.Component<TOwnProps, HocSquareState> {
                // xxx
                render() {
                    const { styles, className } = this.state;
                    const passThroughProps: any = this.props;
                    const classNames = className ? `staticHocWrap ${className}` : "staticHocWrap";
                    const staticProps: WrappedStaticSquareUtils = {
                        changeBackground: this.changeBackground,
                        toTopAnimate: this.toTopAnimate
                    };  // 提供一些可能会用到的改变背景图的方法以及被撞时调用向上动画的方法
    
                    return (
                        <div
                            className={classNames}
                            style={styles}>
                            <Component
                                hoc={staticProps}
                                {...passThroughProps}/>
                        </div>
                    );
                }
            }
    }

3. 性能问题

  • 避免卡顿 前者直接操作某个DOM渲染不会有太多卡顿现象发生
    React使用Mobx, Redux等进行整个游戏数据控制时, 如果不对渲染进行优化, 当store某个属性值变化导致所有接入props的组件都重新渲染一次代价是巨大的!
  1. 采用PureComponent某些组件需要这样写
=>
    class Square extends React.PureComponent<SquareProps, {}> {
        // xxx
    }

其中就需要了解PureComponent。React.PureComponent是2016.06.29 React 15.3中发布。

image

PureComponent改变了生命周期方法shouldComponentUpdate,并且它会自动检查组件是否需要重新渲染。这时,只有PureComponent检测到state或者props发生变化时,PureComponent才会调用render方法,但是这种检查只是浅计较这就意味着嵌套对象和数组是不会被比较的更多信息

  1. 多采用组件去渲染, 对比两种方法
=>
    // 方法1.
    <InGameBG   // 包裹组件负责渲染背景变化相关
            store={store}>
            <Contra // 玩家控制的角色组件
                store={store}/>
            <BulletsMap // 负责渲染子弹
                store={store}/>
            <EnemiesMap // 负责渲染敌方角色
                store={store}/>
    </InGameBG>
    //方法2.
    <InGameBG
        store={store}>
            <Contra
                store={store}/>
            <div>
                {
                    bulletMap.map((bullet, index) => {
                    if ( bullet ) {
                        return (
                            <Bullet
                                key={`Bullet-${index}`}
                                {...bullet}
                                index={index}
                                store={store}/>
                        );
                    }
                    return null;
                })
                }
            </div>
            <EnemiesMap
                store={store}/>
    </InGameBG>

这两种方法的区别就是在于渲染子弹是否通过组件渲染还是在父组件中直接渲染, 其中方法2的性能会有很大的问题, 当某个子弹变化时使得最大的容器重新渲染, 其中所有子组件也会去判断是否需要重新渲染,使得界面会出现卡顿。而方法1则只会在发生数据变化的子弹去渲染。

4. 需要注意的点

  • 及时移除监听, 在组件卸载时需要移除该组件的事件监听, 时间函数等。如游戏开始组件
=>
    class GameStart extends React.Component<GameStartProps, {}> {
        constructor(props) {
            super(props);
    
            this.onkeydownHandle = this.onkeydownHandle.bind(this);
        }
        componentDidMount() {
            this.onkeydown();
        }
        componentWillUnmount() {
            this.destroy();
        }
        destroy(): void {
            console.log("游戏开始! GameStart Component destroy ....");
            window.removeEventListener("keydown", this.onkeydownHandle);
        }
        onkeydownHandle(e: KeyboardEvent): void {
            const keyCode: KeyCodeType = e.keyCode;
            const {  store } = this.props;
            const { updateGameStatus } = store;
            switch ( keyCode ) {
                case 72:
                    updateGameStatus(1);
                    break;
            }
        }
        onkeydown(): void {
            window.addEventListener("keydown", this.onkeydownHandle);
        }
        render() {
            return (
                <div className="gameStartWrap">
                </div>
            );
        }
    }

5. 最近写的 超级魂斗罗 效果与GitHub

超级魂斗罗
超级魂斗罗

https://github.com/xiaoxiaojx/SuperContra

Thank You

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容

  • 原教程内容详见精益 React 学习指南,这只是我在学习过程中的一些阅读笔记,个人觉得该教程讲解深入浅出,比目前大...
    leonaxiong阅读 2,809评论 1 18
  • 深入JSX date:20170412笔记原文其实JSX是React.createElement(componen...
    gaoer1938阅读 8,039评论 2 35
  • It's a common pattern in React to wrap a component in an ...
    jplyue阅读 3,248评论 0 2
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,364评论 25 707
  • 有没有那么一本书让你惦念不忘?有没有那么一个角色让你倍感神奇?有没有那个故事让你大呼过瘾?有没有那么一个人物成为一...
    玩英语阅读 394评论 0 4