DOM+CSS3实现小游戏SwingCopters

  前些日子看到了一则新闻,flappybird原作者将携新游戏SwingCopters来袭,准备再靠这款姊妹篇游戏引爆大众眼球。就是下面这个小游戏:

   

  前者的传奇故事大家都有耳闻,至于这第二个游戏能否更加火爆那是后话了。不过我看了作者的宣传视频后,蠢蠢欲动,这么简单的小游戏我山寨一个网页版出来如何?简单思索一下,打算用DOM+CSS3来实现一个。一来强化一个下自己的CSS3知识,二来也探索下用原生DOM来做动画的性能到底如何。
  三四天后,原作者的SwingCopters貌似没怎么火起来,看来flappybird的神话只是一个偶然呀~不过我的山寨版倒是有模有样的做出来了,点这里查看Demo,请在chrome下打开, 你懂的。

  先来说下整体思路,基本的动画效果,如移动、旋转,用CSS3的transition、transform+keyframes来做,把基本的动画单元做成一个个css类,为元素添加对应的class就可以让它动起来,删除、更改class则可以让元素停止、切换动画。至于什么时候进行切换,一方面是根据用户的操作,另一方面是根据游戏的“主线程”来判断。所谓“主线程”,就是控制游戏画面不停刷新的代码,游戏的主控制逻辑都写在这里,包括场景生成、碰撞检测等。大家都知道动画是由页面的不停重绘来产生的,当每秒的刷新次数达到60时,人眼会感觉到流畅的动画,这也是大多数游戏追求60fps的原因。关于如何做帧刷新有几种方法,具体可参看这里(http://qingbob.com/javascript-high-performance-animation-and-page-rendering/)。我这里采用requestAnimationFrame来做,它的好处是让你用代码来请求一次帧刷新,这样能避免“掉帧”,但是负面影响是,当机器性能不好时,会降低帧率,表现就是你看到游戏的动画变缓慢了。

requestAnimationFrame在PC端的支持还不错,不过在移动端的就有点挫了,Android4.4才支持,所以有必要做一下兼容处理,幸好已经有大神提供代码了,直接拿来用:

(function(){
    var lastTime = 0;
    var vendors = [‘ms‘, ‘moz‘, ‘webkit‘, ‘o‘];
    for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
        window.requestAnimationFrame = window[vendors[x] + ‘RequestAnimationFrame‘];
        window.cancelAnimationFrame = window[vendors[x] + ‘CancelAnimationFrame‘] || window[vendors[x] + ‘CancelRequestAnimationFrame‘];
    }
    if (!window.requestAnimationFrame) window.requestAnimationFrame = function(callback, element) {
        var currTime = new Date().getTime();
        var timeToCall = Math.max(0, 16 - (currTime - lastTime));
        var id = window.setTimeout(function() {
            callback(currTime + timeToCall);
        }, timeToCall);
        lastTime = currTime + timeToCall;
        return id;
    }
    if (!window.cancelAnimationFrame) window.cancelAnimationFrame = function(id) {
        clearTimeout(id);
    }
}());

  下面我把一些技术细节来介绍下,介于小弟也是第一次做游戏,有些地方的实现不免走了弯路,或者损耗性能,有大牛发现了请一定赐教~

自适应的容器
     先从最简单的来说起吧,首先需要一个div来做整个游戏的容器,由于游戏要能在手机上玩,所以宽高就必须做成自适应的,那么viewport的设置是必不可少的:

<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />

  这个不多解释了。div默认宽度100%所以不用管,高度要做到根据屏幕100%显示,我们需要给文档的根节点这样的css代码:

html, body{
     height: 100%;
     position: relative;
     margin: 0;
     overflow: hidden;
     -webkit-user-select:none;
}

  高度100%。定位属性relative,让子元素的定位以它为参照。同时overflow:hidden防止出现滚动条。最后还加了user-select:none,防止用户连续点击的时候出现难看的选区。
     接下来是容器container的样式:

#container{
     height: 100%;
     position: relative;
     overflow: hidden;
}

  这样高度就能充满整个屏幕了。
     另外,为了让游戏在PC浏览器中也可以玩,我又用媒体查询做了如下设置:

@media screen and (min-width: 1024px) {
     #container{
          width: 360px;
          margin: 0 auto;
     }
}

  给容器360像素的宽度并居中对齐。这样在PC浏览器中就不会拉伸的很难看了。

移动的背景
     游戏的容器container有一个背景图片,这个背景图片是需要连续且无限滚动的。首先,图片纵向平铺嘛,一个background-repeat: repeat-y;搞定。原先我考虑这么简单的运动用css3肯定能做的啦,但细细考虑之后发现竟然实现不了。。。假设在keyframes中设置关键帧,改变background-position来实现背景移动,移动倒是没问题,关键是这个连续无限滚动比较棘手,要连续滚动必须给一个很大的值才行,background-position需要设为多大才算无限呢?天知道玩家能玩多长时间,而且这样做显然是不合理的。或者把动画的播放次数设为infinite呢?这也不行,因为每次循环都会从头开始播放一遍,这样背景会闪动。所以最终还是把背景的移动放在js中来操作了,用一个变量来记录背景的位置,然后在主线程中不断递增。大概的代码结构是这样子的:

var game = {
     bgMove : function(){
          posMark += 2;
          container.css(‘background-position‘, ‘0 ‘+posMark+‘px‘);
          timmer = requestAnimationFrame(game.bgMove);
     }
}

  只要调用game.bgMove(),就会通过 requestAnimationFrame来递归调用,用一个全局的变量来标记背景的位置,每次递增,从而不断修改背景位置,实现背景无限移动。

逐步播放动画实现旋转的螺旋桨

     游戏中人物头上的螺旋桨在不停转动,如何实现这个动画呢?其实原理很简单,我们只需准备这样一张图片:

  

  这是向左飞行和向右飞行的几个状态,将它设置为背景图片,然后不停改变背景的位置即可。要注意的是背景位置并不是连续变化,而是在几个值之间“切换”。

     css3的keyframes + animation是通过定义关键帧的方式来实现动画,像flash一样,帧之间的过渡效果由浏览器来替你完成。但我们此处并不想要过渡效果,我们只想让播放两个帧而已。这里要用到animate-timing-function的一个比较特殊的取值:steps(),它可以控制动画最终由多少步来完成。这里我们需要图片中的第一个状态和第二个状态来切换,所以取steps(2)就OK了。代码如下:

  首先我们定义关键帧:

@-webkit-keyframes flyr{
     0%{
          background-position: 0 0;
     }
     100%{
        background-position: -108px 0;
     }
}

  然后定义一个class,只要在元素上加上这个类就可以进行动画了:

.flyr{
     -webkit-animation:flyr 200ms steps(2) 0 infinite;
}

  我直接使用了animation这个混合属性,取值的含义依次是:animation-name(动画名称),animation-duration(动画时间),animation-delay(开始播放时间),animation-iteration-count(播放次数),animation-direction(播放方向),animation-fill-mode(播放后的状态),animation-play-state(设置动画的状态),不写则取默认值。

来看一下效果吧:

  

  向左飞的动画也同理,改变background-position的值即可。我们取名为flyl,只需要让元素的类名在flyl和flyr直接切换,就可以改变飞行的方向,是不是很方便。

在这里需要注意的一点是,steps(2)控制的两步播放,并不是播放0%和100%时的状态,而是根据具体的css属性的值来计算最终播放的两帧是什么状态。你可以自己写个例子看一下,这里不多说了。

起步向上飞行

     人物一开始是在地上站着的,游戏开始时会先上升到半空中,然后垂直位置不再改变。这个比较好做,我们只需定义一个名为up的动画,如下:

@-webkit-keyframes up{
     0%{
          bottom: 0;
     }
     100%{
          bottom: 44%;
     }
}

  然后一块加在flyl类上即可,多个动画用逗号隔开。于是flyl就变成了这样:

.flyl{
    -webkit-animation:flyl 200ms steps(2) 0 infinite, up 4s linear 0 1 normal forwards;
}

  这里animation-iteration-count取值为1,因为只播放一次就可以了。另外要注意的一点是,这一遍播放完后动画应该停留在结束时的状态,所以我们还需设置animation-fill-mod值为forwards。

人物的左右移动

     通过点击改变了飞行方向后,人物会向对应的方向横向移动,这个怎么来做呢?一开始我想简单了,左右移动嘛,跟上升还不是一个道理?于是想当然的定义一个这样的动画:

@-webkit-keyframes mover{
     0%{
          left : 0;
      }
     100%{
          left : 100%;
     }
}

  只需在flyl后面再加个逗号,加上movel就行了。或者定义成一个类,为人物添加这个类来实现向左移动。

但事实证明这样是错误的。因为在实际操作中,改变飞行方向可能发生在任何一刻,而这个时候人物的left值可能是20、50或者其他任何值。我们需要的是在当前left的基础上进行改变,而不是让它先归零。所以这里便不能用keyframes了,因为我们总是无法确定这个初始的left是多少。

这个时候css3的transition就派上用场了,它的作用也是自动创建补间动画,只不过没有animation那么复杂,只需为它指明需要过渡哪些属性就可以了。所以,我的flyl和flyr就变成了这样:

.flyl{
     left: 0 !important;
     -webkit-animation:flyl 200ms steps(2) 0 infinite, up 4s linear 0 1 normal forwards;
}
.flyr{
     left: 100% !important;
     -webkit-animation:flyr 200ms steps(2) 0 infinite, up 4s linear 0 1 normal forwards;
}   

  与此同时,我们的player要加上这一行:

-webkit-transition : left 1.5s 0 linear;

  这样我们巧妙的摆脱了之前的困境,只需指定left即可,管它是从哪个值变来的,交给transition过渡去就好了。

现在只要监听click事件,根据玩家的点击来为人物切换class,我们的就可以来回飞了。js代码如下:

$(document).on(‘click‘, function(){
               if(++direction%2==0){
                    player[0].className = ‘flyl‘;
               }
               else{
                    player[0].className = ‘flyr‘;
               }
          });

  我们用一个变量direction来记录当前的方向,每次点击让它递增,然后根据奇偶性来改变className即可。之所以用变量来记录而不是通过hasClass来判断当前方向的原因是减少DOM访问。

摆锤的产生和移动

     先说摆锤的左右摆动动画,这个其实也不难,用transform:rotate控制旋转一定的角度即可。有一点要注意的是,transform的变形圆点默认是元素的中心位置,而我们的摆锤可不是原地旋转的,所以旋转的中心应该控制在元素的顶部位置,我们用transform-origin来设置变形圆点位置,代码如下:

-webkit-transform-origin:center 4px;

  摆锤是挂在横梁上的,横梁是自上而下移动的,在横梁的移动中其实就包含了我们游戏的主要逻辑:

1. 产生长度随机的横梁

2. 检测摆锤与飞机的碰撞

3. 飞过一层横梁则得分加1

4. 横梁移出屏幕可视范围,remove节点

  这里用纯css实现横梁的移动的话会有一些逻辑无法实现,这中间必须有js来控制的。所以横梁/摆锤的产生就放在了我们游戏的“主线程”里。

简单说下思路:

有两个常量,分别表示横梁之间的水平距离和垂直距离,另外我们还需定义横梁的最小长度和最大长度,在这两个值之间产生一个随机数作为左侧横梁的长度,然后根据水平距离来计算出右侧横梁的长度。

至于碰撞检测,我这里就简单处理了(考虑到这个摆锤在不停的摆动),直接用圆形模型来做,即两个圆心的距离小于半径之和则认为发生了碰撞。

计算得分也比较简单,只要横梁的top值大于飞机的top值了,就认为已经越过了这一道横梁,得分加1.

最后,当横梁的top值大于整个容器的高度时,说明它已经移出可视范围,直接把节点remove掉,避免游戏运行一段时间后,DOM节点太多造成卡顿。

下面是主线程的代码:

bgMove : function(){
          game.generateHand();//产生横梁
          posMark += 2;
          container.css(‘background-position‘, ‘0 ‘+posMark+‘px‘);

          var hands = $(‘.hand_l, .hand_r‘);
          hands.each(function(index, element){
               var _this = $(this),
                    thisTop = parseInt(_this.css(‘top‘));
               if(thisTop>cHeight){
                    _this.remove();
               }
               else{
                    thisTop += 2;
                    _this.css(‘top‘, thisTop+‘px‘);
               }
               if(thisTop>player.offset().top+e1H){
                    //已经位于下方
                    if(!_this.data(‘pass‘) && index%2==0){
                         scroeC.text(++score);
                         _this.data(‘pass‘, 1);
                    }
               }
               else{
                    //碰撞检测
                    if(game.impactCheck(player, _this.find(‘.t‘))){
                         game.stop();
                         return false;
                    }
               }

          });

          timmer = requestAnimationFrame(game.bgMove);
     }

  你会发现里面其实也有好多写的不好的地方,例如每次刷新一帧都会用 $(‘.hand_l, .hand_r‘)把页面上所有的横梁节点都取一遍,这样扫描DOM树挺消耗时间的。完全可以把这些节点存在一个数组里。产生横梁的时候在数组中push,需要remove的时候从数组中删除。

  至此,这个小游戏的关键部分就都完成了。剩下就是游戏的控制部分了,stop、restart什么的,其实只要把控制游戏的参数变量和class重置,cancelAnimationFrame,就ok了。

兼容PC和手机

  这里的兼容主要是指click事件的300ms延迟,由于游戏来说,哪怕是一点点的延迟都会不爽。所以我检测了设备类型,如果是移动端,就绑定touchstart事件,代码片段如下:

isMobile : function(){
          var sUserAgent= navigator.userAgent.toLowerCase(),
          bIsIpad= sUserAgent.match(/ipad/i) == "ipad",
          bIsIphoneOs= sUserAgent.match(/iphone os/i) == "iphone os",
          bIsMidp= sUserAgent.match(/midp/i) == "midp",
          bIsUc7= sUserAgent.match(/rv:1.2.3.4/i) == "rv:1.2.3.4",
          bIsUc= sUserAgent.match(/ucweb/i) == "ucweb",
          bIsAndroid= sUserAgent.match(/android/i) == "android",
          bIsCE= sUserAgent.match(/windows ce/i) == "windows ce",
          bIsWM= sUserAgent.match(/windows mobile/i) == "windows mobile",
          bIsWebview = sUserAgent.match(/webview/i) == "webview";
          return (bIsIpad || bIsIphoneOs || bIsMidp || bIsUc7 || bIsUc || bIsAndroid || bIsCE || bIsWM);
     }

var eventType = this.isMobile() ? ‘touchstart‘ : ‘click‘;
          $(document).on(eventType, function(){
               if(++direction%2==0){
                    player[0].className = ‘flyl‘;
               }
               else{
                    player[0].className = ‘flyr‘;
               }
          });

分享到微博

  为了让游戏易于传播,在网上搜了一段分享到微博的代码,试了一下好用,直接贴过来:

<a id="share" href="javascript:(function(){window.open(‘http://v.t.sina.com.cn/share/share.php?title=网页版SwingCopters,来,看看你有多挫&url=idoube.com/proj/SwingCopters&source=bookmark&pic=http%3A%2F%2Fidoube.com%2Fproj%2FSwingCopters%2FSwingCopters%2Fshot.jpg‘,‘_blank‘,‘width=450,height=400‘);})()">分享到微博</a>

  其实在手机上的话,还应该加上微信分享,但是我在手机上玩了一下这个游戏后,顿时感觉没必要了。因为,手机上,那个卡啊!!fps估计在20左右。配置不错的三星尚且如此,可以想象其他安卓机会是什么情况。

  另一个可喜的是,在iphone上玩竟然很流程!在此也不得不佩服ios对图形渲染的处理。

  不过,如果以后再做这种动画比较多的游戏,我是肯定不会选择用DOM来做了。

总结

  这是楼主第一次写小游戏,虽然最终搞出来的游戏像模像样也能玩,但写的过于仓促,有些知识也没有深究,中间踩了一些坑,整体代码质量也并不高。在这里列一列吧:

  1. 有些动画是用纯css3完成,有些是写在js里,到底动画该如何归类应该细细考虑

  2. 没有进行性能监测,我的机器配置较高,在chrome里可以跑到接近60fps。但感觉代码有些地方效率并不高。在Android机上直接卡爆。

  3. 代码简单,js中用了很多全局变量。因为以前有听人说过,简单的程序直接用全局变量就行,性能高,但没有求证这种说法,不知正确与否,有高手知道请指点。

  4. 对于动画比较多的小游戏,用DOM来做不是一个很好的选择,因为手机上卡,不能在微信里分享,效果直接就大打折扣了。下次试着用canvas来写。

  5. 整个代码还是操作DOM的思维,其实做游戏应该用面向对象的风格来组织代码。

  再次附上游戏地址,欢迎体验:http://idoube.com/proj/SwingCopters/

  最后推荐一个我写css3动画经常参考的一个文档:http://ecd.tencent.com/css3/guide.html

时间: 2024-10-10 01:49:33

DOM+CSS3实现小游戏SwingCopters的相关文章

CSS3实现五子棋Web小游戏,Canvas画布和DOM两种实现,并且具有悔棋和撤销悔棋功能。

用Canvas实现五子棋的思路: 1.点击棋盘,获取坐标x,y,计算出棋子的二维数组坐标i和j, 2.棋子的实现,先arc一个圆,再填充渐变色. 3.下完一步棋后切换画笔和角色. 4.赢法算法的实现:计算出整个15*15的棋盘有多少种赢法,定义一个win[]三维数组,数组的初始化如下. //赢法数组 var wins = []; for (var i = 0; i < 15; i++) { wins[i] = []; for (var j = 0; j < 15; j++) { wins[i]

css3+jquery+js做的翻翻乐小游戏

主要是为了练习一下css3的3D翻转功能,就做了这么个小游戏,做的比较粗糙,但是效果看的见. 主要用到的css3代码如下: html结构: 1 <div class="container"> 2 <div class="side"> 3 4 <div class="front"> 5 <!-- 正面 --> 6 </div> 7 8 <div class="back&qu

JS实现别踩白块小游戏

最近有朋友找我用JS帮忙仿做一个别踩白块的小游戏程序,但他给的源代码较麻烦,而且没有注释,理解起来很无力,我就以自己的想法自己做了这个小游戏,主要是应用JS对DOM和数组的操作. 程序思路:如图:将游戏区域的CSS设置为相对定位.溢出隐藏;两块“游戏板”上分别排布着24块方格,黑色每行随机产生一个,“游戏板”向下滚动并交替显示,将每个操作板的黑块位置存入数组,每次点击时将数组pop出来进行比对(我觉得亮点在这……). 这里是游戏的GitHub地址,大家可以到里点击中部菜单最右边的的Downloa

使用Vue编写点击数字小游戏

使用vue编写一个点击数字计时小游戏,列入你在文本框中输入3,点击开始会生成一个3行3列的表格,表格数据为1-9随机排列,这时候从1开始点击,按顺序点到9,当按正确顺序点击完毕,会提示所用的时间,如果顺序没有按对,会提示游戏结束. 1.首先下载vue源码,下载地址http://cn.vuejs.org 2.jquery是在面向dom操作,而vue是面向数据操作的,所以使用vue最好不要去操作dom,尽量发挥出vue的独到之处,(如果使用过angularjs可能更容易理解) 3.建立一个普通的ht

jquery小游戏之接元宝

相信接元宝的游戏大家都很熟悉了,自从抓住神经猫火了之后,微信游戏越来越多,html5像人们料想的那样逐渐占据舞台.当然由于浏览器兼容的问题,html5的游戏依然只能在移动端大展拳脚,不过没关系.今天博主给大家带来一个jquery接元宝的小游戏,即使在ie6下也可以运行. 不多说先上代码: <!DOCTYPE PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"http://www.w3.org/TR/xhtml1/DTD/xhtml1-t

韩顺平_轻松搞定网页设计(html+css+javascript)_第34讲_js超级玛丽小游戏

韩顺平_轻松搞定网页设计(html+css+javascript)_第34讲_js超级玛丽小游戏_学习笔记_源代码图解_PPT文档整理 分类: PHP 2012-12-12 15:01 4256人阅读 评论(0) 收藏 举报 文西马龙:http://blog.csdn.net/wenximalong/ 采用面向对象思想设计超级马里奥游戏人物(示意图) 怎么用通过按键,来控制图片的位置 这个小游戏,用面向对象会很方便,不用面向对象会很麻烦很麻烦,比如以后要讲解的坦克大战的游戏,要是用纯的面向过程或

消方块小游戏

体验地址: http://www.renyugang.cn/html/h5/ http://www.renyugang.cn/html/h5/oop/ 效果图: 代码: <!DOCTYPE html> <html> <head> <title>H5 Game</title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8&quo

Canvas 2D小游戏开发总结-1

由于需要快速开发 在拿到需求时,并没有时间去学习Cocos2d-JS\Egret\lufy legend这样的H5游戏引擎 于是硬着头皮直接用js建模.响应用户.渲染画面 在此要感谢这篇文章http://www.lostdecadegames.com/how-to-make-a-simple-html5-canvas-game/给我的启发 然后我罗列一下开发过程中遇到的问题,以便更好地完善自己的游戏框架 1.按钮问题dom vs canvas 有时候有代码洁癖,会觉得用canvas做代码看起来干

一个js小游戏----总结

花了大概一天左右的功夫实现了一个js小游戏的基本功能,类似于“雷电”那样的小游戏,实现了随即怪物发生器,碰撞检测,运动等等都实现了,下一个功能是子弹轨迹,还有其他一些扩展功能,没有用库,也没有用webGl之类的,单纯的逻辑+对DOM的操作,算是一次试手吧,之所以没有继续去完善,是因为想要整合一下,各个模块要更清晰,大体的设计是按MVC来的,但是对控制器那一块还不满意,设计过程中比较得意的是碰撞检测吧,因为我用了一个数组来维护怪物的生灭,怪物产生则数组push,怪物消失则用splice来从数组中删