转载

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

本教程使用 React 0.14 版本

一、React基本结构

<!DOCTYPE html> <html> <head>     <script src="../build/react.js"></script>     <script src="../build/react-dom.js"></script>     <script src="../build/browser.min.js"></script> </head> <body> <div id="reactGame"></div> <script type="text/babel">      // ** code ** </script> </body> </html> 

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

<div id="reactGame"></div> 

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

二、创建第一个React组件

<script type="text/babel"> var GameBoard = React.createClass({     getInitialState : function(){         return {             gameState : 0         }     },     render : function(){         return <div className="board">             <div className="roadbed"></div>             <div className="road"}>                 <div className="hero"></div>                 <div className="enemy"></div>             </div>             <span className="start"></span>             <span className="kilo"></span>             <div className="failbub">                 <span className="failtext"></span>                 <span className="retry"></span>             </div>         </div>     } }); ReactDOM.render(<GameBoard/>,document.getElementById("reactGame")); </script> 

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

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

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

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

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

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

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

 <script type="text/babel"> var GameBoard = React.createClass({     getInitialState : function(){         return {             gameState : 0         }     },     gameStart : function(){         this.setState({             gameState : 1         })     },     render : function(){         return  <span className = {this.state.gameState==0?"start":"start hide"} onClick = {this.gameStart}></span>     } }); ReactDOM.render(<GameBoard/>,document.getElementById("reactGame")); </script> 

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

<span class="start"></div> 

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

<span class="start hide"></div> 

简单来说,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 方法为游戏注册一个键盘事件

<script type="text/babel"> var GameBoard = React.createClass({     getInitialState : function(){         return {             gameState : 0,             heroLoc : 0,         }     },     gameStart : function(){         this.setState({             gameState : 1         })     },     gameHandle : function(e){         if(this.state.gameState ==1){             switch(e.keyCode){                 case 37:                     this.setState({heroLoc : 0});                     break;                 case 39:                     this.setState({heroLoc : 1});                     break;             }         }     },     componentDidMount:function(){         window.addEventListener("keydown", this.gameHandle, false);     },     render : function(){         var state = this.state;         return <div className="board">             <div className="roadbed"></div>             <div className="road"}>                 <div className={state.heroLoc==0?"hero left":"hero right"}></div>                 <div className="enemy"></div>             </div>             <span className="start"></span>             <span className="kilo"></span>             <div className="failbub">                 <span className="failtext"></span>                 <span className="retry"></span>             </div>             <span className = {state.gameState==0?"start":"start hide"} onClick = {this.gameStart}></span>         </div>     } }); ReactDOM.render(<GameBoard/>,document.getElementById("reactGame")); </script> 

为 componentDidMount 注册监听了键盘事件

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

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

<div class="hero left"></div> 

heroLoc == 1 则结构为

<div class="hero right"></div> 

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

第三步:创建敌人赛车

敌方车辆与主角运动方向一致,都是向上运动,由于主角相对固定,速度又比敌方车辆块,所以 enemy 的运动方向实际是向下运动,直至消失在屏幕之外

为了降低复杂度,我们规定屏幕上每次只会出现一辆敌方车辆,方向随机,所以我们只需一个div作为敌方小车,在小车运动离开屏幕后,马上随机给小车换上不同的车型和方向的 class。

我们给 enemy 加上从0到1000px的运动动画,运动持续时间1s

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

javascript部分

<script type="text/babel"> var GameBoard = React.createClass({     getInitialState : function(){         return {             gameState : 0,             heroLoc : 0,             enemyLoc: 0,             enemyType : 0,             aniEnd : true,         }     },     gameStart : function(){         this.setState({             gameState : 1         });         createEnemy();     },     gameHandle : function(e){         if(this.state.gameState ==1){             switch(e.keyCode){                 case 37:                     this.setState({heroLoc : 0});                     break;                 case 39:                     this.setState({heroLoc : 1});                     break;             }         }     },     createEnemy : function(){         var that = this,         var enemyClass,enemyLoc,enemyType,               animationEnd = true;         setInterval(function(){             if(that.state.aniEnd && that.state.gameState == 1){                 that.setState({aniEnd : false});                 enemyType = Math.floor(Math.random()*3);                 enemyLoc = Math.round(Math.random());                 that.setState({enemyLoc : enemyLoc});                 that.setState({enemyType : enemyType});             }         },1000);         that.refs.enemy.addEventListener("webkitAnimationEnd",function(){             that.setState({aniEnd : true});         });     },     componentDidMount:function(){         window.addEventListener("keydown", this.gameHandle, false);     },     render : function(){         var state = this.state;         var enemyCls = state.gameStart == 0 ?"enemy":("enemy enemy"+ state.enemyType  + " loc" + state.enemyLoc);         return <div className="board">             <div className="roadbed"></div>             <div className="road"}>                 <div className={state.heroLoc==0?"hero left":"hero right"}></div>                 <div className={enemyCls}></div>             </div>             <span className="start"></span>             <span className="kilo"></span>             <div className="failbub">                 <span className="failtext"></span>                 <span className="retry"></span>             </div>             <span className = {state.gameState==0?"start":"start hide"} onClick = {this.gameStart}></span>         </div>     } }); ReactDOM.render(<GameBoard/>,document.getElementById("reactGame")); </script> 

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

第四步:碰撞检测

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

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

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

<script type="text/babel"> var Tick; var GameBoard = React.createClass({     getInitialState : function(){         return {             gameState : 0,             heroLoc : 0,             enemyLoc: 0,             enemyType : 0,             aniEnd : true,         }     },     gameStart : function(){         this.setState({             gameState : 1         });         createEnemy();         this.gameTick();     },     gameHandle : function(e){         if(this.state.gameState ==1){             switch(e.keyCode){                 case 37:                     this.setState({heroLoc : 0});                     break;                 case 39:                     this.setState({heroLoc : 1});                     break;             }         }     },     createEnemy : function(){         var that = this,         var enemyClass,enemyLoc,enemyType,               animationEnd = true;         setInterval(function(){             if(that.state.aniEnd && that.state.gameState == 1){                 that.setState({aniEnd : false});                 enemyType = Math.floor(Math.random()*3);                 enemyLoc = Math.round(Math.random());                 that.setState({enemyLoc : enemyLoc});                 that.setState({enemyType : enemyType});             }         },1000);         that.refs.enemy.addEventListener("webkitAnimationEnd",function(){             that.setState({aniEnd : true});         });     },     gameTick : function(state){         var that = this,             crash = 620,             heroLoc,enemyLoc,trs,dis,kilometer = 0;         if(state){             Tick = setInterval(function(){                 trs = window.getComputedStyle(that.refs.enemy,null).getPropertyValue("transform");                 dis = trs.split(",")[5].replace(")","");                 heroLoc = that.state.heroLoc;                 enemyLoc = that.state.enemyLoc;                 if(dis>crash &&dis<(crash+220) && heroLoc == enemyLoc){                     that.gameOver();                 }                 kilometer ++;                 that.setState({kilometer:kilometer});             },10);         }else{             clearInterval(Tick);         }     },     gameOver : function(){         this.setState({gameState : 0});         this.gameTick(false);     },     componentDidMount:function(){         window.addEventListener("keydown", this.gameHandle, false);     },     render : function(){         var state = this.state;         var enemyCls = state.gameStart == 0 ?"enemy":("enemy enemy"+ state.enemyType  + " loc" + state.enemyLoc);         return <div className="board">             <div className="roadbed"></div>             <div className="road"}>                 <div className={state.heroLoc==0?"hero left":"hero right"}></div>                 <div className={enemyCls}></div>             </div>             <span className="start"></span>             <span className="kilo">{state.kilometer}</span>             <div className="failbub">                 <span className="failtext"></span>                 <span className="retry"></span>             </div>             <span className = {state.gameState==0?"start":"start hide"} onClick = {this.gameStart}></span>         </div>     } }); ReactDOM.render(<GameBoard/>,document.getElementById("reactGame")); </script> 

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

<span className="kilo">{state.kilometer}</span> 

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

四、更多细节

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

<script type="text/babel"> var Tick; var GameBoard = React.createClass({     getInitialState : function(){         return {             gameState : 0,             heroLoc : 0,             enemyLoc: 0,             enemyType : 0,             aniEnd : true,         }     },     gameStart : function(){         this.setState({             gameState : 1         });         createEnemy();         this.gameTick();     },     gameHandle : function(e){         if(this.state.gameState ==1){             switch(e.keyCode){                 case 37:                     this.setState({heroLoc : 0});                     break;                 case 39:                     this.setState({heroLoc : 1});                     break;                 case 32:                     if(this.state.hasSuper==1){                         this.setState({superMode : 1});                         this.setState({hasSuper : 0});                     }                     break;             }         }     },     superBuff : function(){         var that = this;         that.setState({chunge : 1});         setTimeout(function(){             that.setState({chunge : 0});         },1000);     },     superMode : function(){         var that = this;         that.setState({hasSuper : 1});         setTimeout(function(){             that.setState({superMode : 0});         },5000);     },     createEnemy : function(){         var that = this,         var enemyClass,enemyLoc,enemyType,               animationEnd = true;         setInterval(function(){             if(that.state.aniEnd && that.state.gameState == 1){                 that.setState({aniEnd : false});                 enemyType = Math.floor(Math.random()*3);                 enemyLoc = Math.round(Math.random());                 that.setState({enemyLoc : enemyLoc});                 that.setState({enemyType : enemyType});             }         },1000);         that.refs.enemy.addEventListener("webkitAnimationEnd",function(){             that.setState({aniEnd : true});         });     },     gameTick : function(state){         var that = this,             crash = 620,             heroLoc,enemyLoc,trs,dis,kilometer = 0;         if(state){             Tick = setInterval(function(){                 trs = window.getComputedStyle(that.refs.enemy,null).getPropertyValue("transform");                 dis = trs.split(",")[5].replace(")","");                 heroLoc = that.state.heroLoc;                 enemyLoc = that.state.enemyLoc;                 if(dis>crash &&dis<(crash+220) && heroLoc == enemyLoc){                     that.gameOver();                 }                 kilometer ++;                 that.setState({kilometer:kilometer});                 if(kilometer%1000==0){                     that.superMode();                 }             },10);         }else{             clearInterval(Tick);         }     },     gameOver : function(){         this.setState({gameState : 0});         this.gameTick(false);     },     componentDidMount:function(){         window.addEventListener("keydown", this.gameHandle, false);     },     render : function(){         var state = this.state;         var enemyCls = state.gameStart == 0 ?"enemy":("enemy enemy"+ state.enemyType  + " loc" + state.enemyLoc);         return <div className="board">             <div className="roadbed"></div>             <div className="road"}>                 <div className={state.heroLoc==0?"hero left":"hero right"}></div>                 <div className={enemyCls}></div>             </div>             <span className="start"></span>             <span className="kilo"></span>             <div className="failbub">                 <span className="failtext">{state.kilometer}</span>                 <span className="retry"></span>             </div>             <span className = {state.gameState==0?"start":"start hide"} onClick = {this.gameStart}></span>         </div>     } }); ReactDOM.render(<GameBoard/>,document.getElementById("reactGame")); </script> 

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

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 完成了一个相对完整的休闲小游戏

原文  https://blog.coding.net/blog/react-simple-game
正文到此结束
Loading...