React 开发一款简单的赛车游戏

3,646 阅读5分钟
原文链接: blog.coding.net

写在开始之前

最近研究egret引擎时,在论坛看到了用egret引擎写的一款赛车游戏

figure

玩法很简单,左右控制赛车躲避来车,碰撞即游戏失败

下面将为大家一步步讲解,如何用React写出一款纯 javascript+css 的小游戏

准备工作

本教程使用 React 0.14 版本

一、React基本结构




    
    
    


可以看到 html 代码非常简单,我们只留了一个

作为React渲染后插入的节点,所有的代码均写在JS中。
大家注意到 script 标签的 type 为 text/babel ,由 于React 使用 JSX 语法,browser.min.js 用于将 JSX 语法转化为 javascript 语法。

二、创建第一个React组件


我们通过样式创建了一个基础的游戏界面:游戏容器 [board],路面 [roadbed],路面范围 [road],主角车 [hero],敌车 [enemy],还有公里板 [kilo],失败提示 [failbub]

我们创建了一个 GameBoard 的组件,用于建立整个游戏场景,你也可以建立多个子组件,比如主角赛车,敌方赛车,公里板,再在 Gameboard 中引入子组件。

本教程案例相对简单,我们只创建一个组件,也能更容易理解代码逻辑。

React 自带了一些事件处理函数,如

getInitialState()    //组件初始化数据
componentWillMount() //组件渲染前调用
componentDidMount()  //组件渲染后调用
render()             //组件渲染

而 render 函数中,将返回我们页面所有的 html 结构

下面我们用一个简单的例子帮助大家理解 React 的工作流程

 

在上面的例子中,我们在 getInitialState 函数中初始化了 state.gameState:0,在 render 函数中,我们对 div 的 className 做了一个三元表达式的判断,如果 gameState 为0,表示游戏未开始,渲染出的 html 结构为

我们声明一个自定义的方法 gameStart ,在开始按钮上绑定 onClick 事件,调用 gameState,执行了 setState 方法,将 state 对象中的 gameState 设置为1,由于调用了 setState ,render 函数立即更新,此时 gameState 值为1,渲染出的 html 结构为(开始按钮隐藏)

 
 

简单来说,React 有个全局的状态 state,我们通过 setState 去改变 state 对象的值,一旦执行了 setState,React 立即触发 render 函数,通过内部的 diff 算法,判断当前 DOM 是否发生改变,
改变即更新到真实 DOM 中。

三、游戏开始!

现在我们开始让游戏跑起来,为了避免频繁的操作 DOM 结点影响性能,所有动态效果均由 css3 实现。

第一步:马路移动

首先我们对游戏的动画效果进行分析,小车固定在屏幕底部,所以我们制作一个马路向下运动的循环动画,看起来就像小车在向上跑

.roadbed{
    background:url(../resource/road.png) repeat-y;
    width:480px;
    height:1600px;
    position:absolute;
    left:0;
    top:-800px;
}
.roadRun{
    -webkit-transform:translateZ(0);
    -webkit-animation:roadRun 1.2s linear infinite;
}
@-webkit-keyframes roadRun{
    100%{ -webkit-transform:translateY(800px);}
}

我们也可以使用 background-position 动画,但使用 transform 动画更加流畅。

第二步:控制小车移动

我们通过控制键盘的左右方向键来控制赛车的左右位置,当按下左方向键,我们给 hero 节点加上 left ,按下右方向键,加上 right ,在 css 中控制 hero.left 和 hero.right 的位置

在 render 渲染后,我们调用 componentDidMount 方法为游戏注册一个键盘事件


为 componentDidMount 注册监听了键盘事件

componentDidMount:function(){
       window.addEventListener("keydown", this.gameHandle, false);
}

当按下键盘按键后,调用 gameHandle 方法,判断按键 keyCode ,如果按下了左方向键(keyCode:37),则设置 heroLoc:0,
按下右方向键(keyCode:39),设置 heroLoc:1,render 方法再次更新,判断 heroLoc 值,如果 heroLoc == 0,则主角结构为

heroLoc == 1 则结构为

至此,我们完成了小车的基本移动操作

第三步:创建敌人赛车

敌方车辆与主角运动方向一致,都是向上运动,由于主角相对固定,速度又比敌方车辆块,所以 enemy 的运动方向实际是向下运动,直至消失在屏幕之外
为了降低复杂度,我们规定屏幕上每次只会出现一辆敌方车辆,方向随机,所以我们只需一个div作为敌方小车,在小车运动离开屏幕后,马上随机给小车换上不同的车型和方向的 class。
我们给 enemy 加上从0到1000px的运动动画,运动持续时间1s

.enemy{ -webkit-animation:enemy 1s linear; }
@-webkit-keyframes enemy{
    100%{ -webkit-transform:translateY(1000px);}
}

javascript部分


游戏开始后,调用 createEnemy,每隔1s并确保动画执行完毕后,重新为 enemy 设置随机的方向和车型。
至此,我们已经完成马路的运动,主角的控制和敌方的创建。

第四步:碰撞检测

游戏已经成功跑起来了,但仅仅是一些控制操作和效果动画的运行,并没有核心的游戏逻辑,下面我们加入游戏的核心逻辑,碰撞检测
如何判断主角与敌方小车碰撞到一起了?其实思路很简单,我方小车与敌方小车位于同一车道,且敌方小车的运动距离大于舞台高度-我方小车高度,即两车相撞

这个值我们计算出来写死就行,也可以通过 javascript 计算。

大家都知道,大部分游戏都需要一个不断刷新的定时器实时获取和更新状态,即游戏刷新频率(正常为60HZ)
所以我们设置一个定时器 Tick,来实时获取敌方小车与我方小车的方向与位置数据,判断小车是否相撞


这里我们顺便把公里数实时更新

{state.kilometer}

至此,我们已经完成了一个相对完整的小游戏

四、更多细节

游戏仅仅是可玩远远不够,我们可以慢慢加入一些细节提高游戏性,比如让敌方车辆的速度随机,出现的频率随游戏难度增加,
每跑1000km获得一次无敌模式,开启后5s内可以随意碰撞:


最后加入重力感应,控制小车运动,让游戏在移动端解放双手:

window.addEventListener("devicemotion", function(event) {
            var eventaccelerationIncludingGravity = event.accelerationIncludingGravity;
            if(that.state.gameState == 1){
                if(eventaccelerationIncludingGravity.x < -1){
                    that.setState({heroLoc : 0});
                }else if(eventaccelerationIncludingGravity.x > 1){
                    that.setState({heroLoc : 1});
                }
            }
}, false);

至此,我们用 React 完成了一个相对完整的休闲小游戏