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

前端 开发插件vim
谷歌前端开发插件
前端调用接口的开发的插件

本教程更多在于展示React的特性和游戏编写过程中的思维逻辑,如有错误欢迎拍砖。
下面将为大家一步步讲解,如何用React写出一款纯js+css的小游戏

准备工作

本教程游戏素材下载:【素材下载
本教程使用React 0.14版本,对React不熟悉的童鞋可以先看下阮一峰React入门,或者极客学院的视频教程

一、React基本结构

html 代码片段

<!DOCTYPE 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组件

html 代码片段

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

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

我们创建了一个GameBoard的组件,用于建立整个游戏场景,你也可以建立多个子组件,比如主角赛车,敌方赛车,公里板,再在Gameboard中引入子组件,
本教程案例相对简单,我们只创建一个组件,也能更容易理解代码逻辑。
React自带了一些事件处理函数,如
getInitialState() : 组件初始化数据
componentWillMount() : 组件渲染前调用
componentDidMount() : 组件渲染后调用
render() : 组件渲染

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

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

<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}
                />
            )
        },
    })
    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实现。

第一步:马路移动

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

.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方法为游戏注册一个键盘事件
html 代码片段

<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注册监听了键盘事件
[quote]componentDidMount:function(){
window.addEventListener(“keydown”, this.gameHandle, false);
}[/quote]
当按下键盘按键后,调用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
css 代码片段

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

html 代码片段

<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设置随机的方向和车型。
至此,我们已经完成马路的运动,主角的控制和敌方的创建。

第四步:碰撞检测

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

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

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

html 代码片段

<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”></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>

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

<span className=”failtext”>{state.kilometer}</span>
至此,我们已经完成了一个相对完整的小游戏。

四、More ditails!

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

<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>

也可以加入重力感应,控制小车运动,让游戏在移动端解放双手:
[quote]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);

web前端开发流行插件
chorme插件前端开发
sublime 前端开发插件
» 本文来自:前端开发者 » 《React开发一款简单的赛车游戏》
» 本文链接地址:https://www.rokub.com/6431.html
» 您也可以订阅本站:https://www.rokub.com
赞(0)
64K

评论 抢沙发

评论前必须登录!