继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

用HTML+CSS+JQ做坦克大战(阉割版)

fhw2087
关注TA
已关注
手记 14
粉丝 176
获赞 2138

各位小伙伴们,大家好,现在为大家带来阉割版坦克大战的最后一部分-射击脚本与破墙脚本的制作。做到这里,我不想再做下去了,因为一个简单的动作都需要上百条脚本的反复运行,我的小笔记本明显感觉到了CPU使用率的增加。一方面,我的代码功底还处于初学阶段,有很多不知道的和需要改善的,写这个坦克大战也仅仅是为了学习Jquery,能坚持写到这里,应该已经入门了。继续写下去不是很现实,单单依靠JQ去开发一个“复杂”的游戏,不现实,想开发游戏的话应该使用更先进的专门框架。以后我会继续写一些手记,分享我学习过程中的心得和知识,大家可以关注我的慕课网帐号。好了,废话不多说,下面开始本节内容。


主要知识点介绍


string.substr(start,length)
字符串的截取操作,length如果不传入参数,代表从start位置截取之后所有的字符串。
var str1='abcdefg';
var str2=str1.substr(1,4);//运行结果,str1='abcdefg',str2='bcde';
var str3=str1.substr(1);//运行结果,str3=‘bcdefg’;
注意与另一个string.substring(start,stop)的区别
var str1='abcdefg';
var str2=str1.substring(1,4);//运行结果,str1='abcdefg',str2='bcd';
var str3=str1.substring(1);//运行结果,str3=‘bcdefg’;


string.split(str)
将字符串,以str为标记,分割成数组。
var str='ab c+ab c#ab c';
str.split('');//传入空字符串,结果["a", "b", " ", "c", "+", "a", "b", " ", "c", "#", "a", "b", " ", "c"];
str.split(' ');//传入空格字符串,结果["ab", "c+ab", "c#ab", "c"];
str.split('+');//传入‘+’字符串,结果["ab c", "ab c#ab c"];
str.split('wobucunzai');//传入一个不存在原字符串中的标记,结果["ab c+ab c#ab c"];
联想另一个方法,array.join('str')
将数组,转化成字符串
var arr=['a','b','c','d','e','f'];
arr.join();//什么也不传,结果"a,b,c,d,e,f";
arr.join(',');//传入逗号,结果与什么也不传一致,因此不传=默认逗号分割;
arr.join('');//传入空字符串‘’,结果“abcdef”;
arr.join('+');//传入‘+’,结果"a+b+c+d+e+f";


.height()与.width()
.height()等价于.css('height');
.width()等价于.css('width');
不传参数,是获取数值,传入参数,是修改数值。


DOM.hasClass('classname')
判断当前DOM是否拥有一个‘classname’的class,返回一个布尔值。


DOM.removeClass('classname')////DOM.addClass('classname')
从当前DOM中移除///添加一个class


子弹的HTML和CSS


<div class="zhanchang" id="zhanchang">
        <div class="tanker" id="yyf"></div>
        <div class="zidan" id="yyf_zidan"></div>//子弹元素和坦克是放在一个父元素下的
        <div class="tanker" id="longdd"></div>
        <div class="zidan" id="longdd_zidan"></div>
        <div class="tanker" id="bianji">
            <div class="guangbiao" id="guangbiao"></div>
        </div>
</div>

.zidan{ width:8px; height:19px; background:url(../img/daodang.png); position:absolute;top:0; left:0px;opacity:0;}
一个8*19的矩形,里面有一个导弹背景图片,绝对定位于左上角,默认透明;

图片描述
这里,我把子弹的opacity=1,让大家看看子弹原来是在哪个位置的!


moveto(id1,id2,pos)填装炮弹函数


function moveto(id1,id2,pos){//装弹脚本,将id1炮弹移动到id2坦克的pos相对位置
    var _deg=rotatedeg(id2);//获取坦克的朝向,rotatedeg(id),是一个自定义函数,返回1-4的数字,代表坦克的朝向;
    var $_left;
    var $_top;//定义填装后子弹的位置
    var $halfw=$(id1).css('width').replace('px','')/2;
    var $halfh=$(id1).css('height').replace('px','')/2;//定义半宽半高,方便下面计算
    switch(_deg){//switch-case方法,分别写4个方向的装弹
        case 1://坦克头向上,装弹
            $_left=$(id2).position().left+pos.left-$halfw;
            $_top=$(id2).position().top+pos.top;
            $(id1).css({'left':$_left,'top':$_top,'transform':'rotate(0deg)'});
            break;//怎么计算的距离,不细讲了,有兴趣的同学可以自己比划比划
        case 2://向下
            $_left=$(id2).position().left+pos.left-$halfw;
            $_top=$(id2).position().top+50-pos.top-2*$halfh;
            $(id1).css({'left':$_left,'top':$_top,'transform':'rotate(180deg)'});
            break;
        case 3://左
            $_top=$(id2).position().top+pos.left-$halfh;
            $_left=$(id2).position().left+pos.top+$halfh-$halfw+4.5;
            $(id1).css({'left':$_left,'top':$_top,'transform':'rotate(270deg)'});
            break;
        case 4://右
            $_top=$(id2).position().top+pos.left-$halfh;
            $_left=$(id2).position().left+50-pos.top-$halfh-$halfw+5.5;
            $(id1).css({'left':$_left,'top':$_top,'transform':'rotate(90deg)'});
            break;
    }
}
上面的图片中,我们手动执行一次装弹函数moveto('#yyf_zidan',‘#yyf’,{'left':25,'top':1});

图片描述
看到了吧,炮弹已经装到炮筒里了,哈哈哈哈o(∩_∩)o


rotatedeg(id),判断坦克朝向函数


function rotatedeg(id){//判断坦克朝向函数,id是坦克id
    var $_deg=$(id).css('transform').substr(7).split(', ');//获取矩阵字符串,转化成数组
    if($_deg[0]==1$_deg[0]=='') return 1;//判断朝上,返回1
    else if($_deg[0]==-1) return 2;//判断朝下,返回2
    else if($_deg[2]==1) return 3;//判断朝左,返回3
    else if($_deg[1]==1) return 4;//判断朝右,返回4
}
这里的$(id).css('transform')返回值是一个矩阵字符串,我靠,而且在火狐和其它浏览器中返回的不一样;
朝上:.css('transform')  火狐返回"matrix(1, 0, 0, 1, 0, 0)"IE谷歌返回"matrix(1, 0, 0, 1, 0, 0)";
朝下:.css('transform')  火狐返回"matrix(-1, 0, 0, -1, 0, 0)"IE谷歌返回"matrix(-1, 1.22465e-16, -1.22465e-16, -1, 0, 0)",竟然有这样的浮点数,@¥%¥@%,欺负我不懂矩阵;
朝左:.css('transform')  火狐返回"matrix(0, -1, 1, 0, 0, 0)"IE谷歌返回"matrix(-1.83697e-16, -1, 1, -1.83697e-16, 0, 0)";
朝右:.css('transform')  火狐返回"matrix(0, 1, -1, 0, 0, 0)"IE谷歌返回"matrix(6.12323e-17, 1, -1, 6.12323e-17, 0, 0)";
因此没有办法,要做到兼容,必须处理一下这个字符串,我们对字符串做.substr(7).split(', ')处理后:
朝上:火狐[1,0,0,1,0,'0)'],IE谷歌[1,0,0,1,0,'0)']];  arr[0]都为1
朝下:火狐[-1,0,0,-1,0,'0)'],IE谷歌[-1,浮点数,浮点数,-1,0,'0)']];  arr[0]都为-1
朝左:火狐[0,-1,1,0,0,'0)'],IE谷歌[浮点数,-1,1,浮点数,0,'0)']];  arr[2]都为1
朝右:火狐[0,1,-1,0,0,'0)'],IE谷歌[浮点数,1,-1,浮点数,0,'0)']];  arr[1]都为1
经过这个转化后,就可以兼容的判断旋转角度了。

shot(id)开炮脚本
图片描述

分析一下,子弹要移动,其实和坦克要移动是一样样的,必须事先计算好子弹能移动的最大的距离,这里的计算方式与上节内容中计算坦克移动的方式基本一致。首先计算子弹所在的行或列,然后选中所在行列中的墙,最后计算出距离。
子弹的射击有一个前提条件,一个坦克同时只能发射一颗子弹,也就是说只有子弹飞在空中,坦克将不能继续执行发射命令。


function shot(id){//开炮脚本
    /*获取开炮时炮弹坐标和炮弹方向*/
    if($('#yyf_zidan').css('opacity')==0){//如果场面上没有子弹,执行装弹函数
        moveto('#yyf_zidan',id,{'left':25,'top':1});
    }//装弹
    var $tanker=$(id);
    var $zidan=$('#yyf_zidan');//定义坦克和子弹
    var $zidan_x=Math.ceil($zidan.position().left/50);//计算坦克所在列
    var $zidan_y=Math.ceil($zidan.position().top/50);//计算坦克所在行
    var $zidan_x2=Math.ceil(($zidan.position().left+8)/50);//计算坦克所在列2
    var $zidan_y2=Math.ceil(($zidan.position().top+8)/50);//计算坦克所在行2
    var $zidan_left=$zidan.position().left;
    var $zidan_right=$zidan.position().left+8;
    var $zidan_top=$zidan.position().top;
    var $zidan_bottom=$zidan.position().top+8;//定义子弹4个边距,方便计算
    var nn=rotatedeg(id);//计算坦克的朝向,决定到底是向哪个方向开炮
上面的代码是对射击函数中,一些通用变量的定义和计算;
下面用switch-case方法,写4个方向的设计函数
switch(nn){
        case 1://向上开炮
            /*获取炮弹运动的最大距离$blank*/
            if($zidan.css('opacity')==0){//如果场面上没子弹,进行操作
                var $wall=chooselie($zidan_x).add(chooselie($zidan_x2));//将子弹所在列中的所有墙选中,加入到$wall中;
                var $wall2=[];
                $wall.each(function(index, element) {
                    if(!($(this).position().left>=$zidan_right$(this).position().left+$(this).width()<=$zidan_left)){
                        $wall2.push($(this)[0]);
                    }
                });
                $wall=$($wall2);//这步操作也不多解释了,目的是把挡不住子弹飞行的墙过滤掉。
                var $zidantop=$tanker.position().top;
                var $blank=$zidantop;//定义子弹飞行的距离,赋一个初始值
                $wall.each(function(index, element) {
                    if(!$(this).hasClass('riverqiang')){
                        var $top=$(this).position().top+$(this).height();
                        if($zidantop-$top<$blank&&$zidantop-$top>=0) $blank=$zidantop-$top;
                    }
                });//遍历所有墙,如果有强能挡住子弹,更新子弹能移动的距离$blank;
            /*开炮*/
                $zidan.css('opacity',1);//将子弹实体化,可以看见了,之前一直是透明的
                var $_top=$tanker.position().top-$blank;
                var obj={top:$_top};
                $zidan.animate(obj,3*($blank+1),'linear',function(){//子弹飞行动画
                    var $zidanpos1=$zidan.position().left+4;
                    var $zidanpos2=$zidan.position().top;//计算子弹飞行结束后,子弹头的位置,目的是为了判断子弹最后碰到的是什么墙,如果是砖墙的话,要执行破坏砖墙的脚本;
                    var zhuanqiangeq=jizhongzuobiao(nn,[$zidanpos1,$zidanpos2]);//通过自定义函数jizhongzuobiao(p,pos)计算,坦克子弹击中的墙的index,将返回的index数组,储存在zhuanqiangeq中;
                    for(var i=0;i<zhuanqiangeq.length;i++){//遍历这个数组
                        var zhuanqiang=$('.map_i').eq(zhuanqiangeq[i]);//定义对应的砖墙
                        if(zhuanqiang.height()==50){//如果这个砖墙是50高度,那么高度减半,表示砖墙被打掉了一半的高度
                            zhuanqiang.height(25);
                        }
                        else{//如果砖墙已经是一半高度了,那直接移除这个砖墙
                            zhuanqiang.removeClass('zhuan_qiang').height(50);
                        }
                    }
                    $zidan.attr('style','');//最后,销毁子弹,实际上就是把子弹放到初始位置隐藏。
                });
            }
            break;
这是switch-case中的case1,也就是向上开炮,其中,当子弹击中墙壁后,有一个破坏砖墙的脚本,我们后面再讲;下面继续完善后3个case
case 2://向下开炮
            if($zidan.css('opacity')==0){
                var $wall=chooselie($zidan_x).add(chooselie($zidan_x2));
                var $wall2=[];
                $wall.each(function(index, element) {
                    if(!($(this).position().left>=$zidan_right$(this).position().left+$(this).width()<=$zidan_left)){
                        $wall2.push($(this)[0]);
                    }
                });
                $wall=$($wall2);
                var $zidanbottom=550-$tanker.position().top;
                var $blank=$zidanbottom;
                $wall.each(function(index, element) {
                    if(!$(this).hasClass('riverqiang')){
                        var $bottom=600-$(this).position().top;
                        if($zidanbottom-$bottom<$blank&&$zidanbottom-$bottom>=0) $blank=$zidanbottom-$bottom;
                    }
                });

                var $_bottom=$zidan.position().top+$blank+1;
                var obj={top:$_bottom};
                $zidan.css('opacity',1);
                $zidan.animate(obj,3*($blank+1),'linear',function(){
                    var $zidanpos1=$zidan.position().left+4;
                    var $zidanpos2=$zidan.position().top;
                    var zhuanqiangeq=jizhongzuobiao(nn,[$zidanpos1,$zidanpos2]);
                    for(var i=0;i<zhuanqiangeq.length;i++){
                        var zhuanqiang=$('.map_i').eq(zhuanqiangeq[i]);
                        if(zhuanqiang.height()==50){
                            zhuanqiang.height(25).css('top','+=25');
                        }
                        else{
                            zhuanqiang.removeClass('zhuan_qiang').css('top','-=25').height(50);
                        }
                    }
                    $zidan.attr('style','');
                });
            }
            break;
case 3://向左开炮
            if($zidan.css('opacity')==0){
                var $wall1=$('.map_i').slice(17*$zidan_y-17,17*$zidan_y).filter('[class*="qiang"]');
                var $wall2=$('.map_i').slice(17*$zidan_y2-17,17*$zidan_y2).filter('[class*="qiang"]');
                var $wall=$wall1.add($wall2);
                var $wall2=[];
                $wall.each(function(index, element) {
                    if(!($(this).position().top>=$zidan_bottom$(this).position().top+$(this).height()<=$zidan_top)){
                        $wall2.push($(this)[0]);
                    }
                });
                $wall=$($wall2);
                var $zidanleft=$tanker.position().left;
                var $blank=$zidanleft;
                $wall.each(function(index, element) {
                    if(!$(this).hasClass('riverqiang')){
                        var $left=$(this).position().left+$(this).width();
                        if($zidanleft-$left<$blank&&$zidanleft-$left>=0) $blank=$zidanleft-$left;
                    }
                });

                var $_left=$zidan.position().left-$blank;
                var obj={left:$_left};
                $zidan.css('opacity',1);
                $zidan.animate(obj,3*($blank+1),'linear',function(){
                    var $zidanpos1=$zidan.position().left;
                    var $zidanpos2=$zidan.position().top+4;
                    var zhuanqiangeq=jizhongzuobiao(nn,[$zidanpos1,$zidanpos2]);
                    for(var i=0;i<zhuanqiangeq.length;i++){
                        var zhuanqiang=$('.map_i').eq(zhuanqiangeq[i]);
                        if(zhuanqiang.width()==50){
                            zhuanqiang.width(25);
                        }
                        else{
                            zhuanqiang.removeClass('zhuan_qiang').width(50);
                        }
                    }
                    $zidan.attr('style','');
                });
            }
            break;
case 4://向右开炮
            if($zidan.css('opacity')==0){
                var $wall1=$('.map_i').slice(17*$zidan_y-17,17*$zidan_y).filter('[class*="qiang"]');
                var $wall2=$('.map_i').slice(17*$zidan_y2-17,17*$zidan_y2).filter('[class*="qiang"]');
                var $wall=$wall1.add($wall2);
                var $wall2=[];
                $wall.each(function(index, element) {
                    if(!($(this).position().top>=$zidan_bottom$(this).position().top+$(this).height()<=$zidan_top)){
                        $wall2.push($(this)[0]);
                    }
                });
                $wall=$($wall2);
                var $zidanright=800-$tanker.position().left;
                var $blank=$zidanright;
                $wall.each(function(index, element) {
                    if(!$(this).hasClass('riverqiang')){
                        var $right=850-$(this).position().left;
                        if($zidanright-$right<$blank&&$zidanright-$right>=0) $blank=$zidanright-$right;
                    }
                });

                var $_right=$zidan.position().left+$blank+1;    
                var obj={left:$_right};
                $zidan.css('opacity',1);
                $zidan.animate(obj,3*($blank+1),'linear',function(){
                    var $zidanpos1=$zidan.position().left+19;
                    var $zidanpos2=$zidan.position().top+4;
                    var zhuanqiangeq=jizhongzuobiao(nn,[$zidanpos1,$zidanpos2]);
                    for(var i=0;i<zhuanqiangeq.length;i++){
                        var zhuanqiang=$('.map_i').eq(zhuanqiangeq[i]);
                        if(zhuanqiang.width()==50){
                            zhuanqiang.width(25).css('left','+=25');
                        }
                        else{
                            zhuanqiang.removeClass('zhuan_qiang').css('left','-=25').width(50);
                        }
                    }
                    $zidan.attr('style','');
                });
            }
            break;
    }
}
看到这么多,是不是想哭,每个方向射击的原理都是一样的,大家向深入了解就参照case1,只是计算过程都要比划比划再比划,当时写的时候一脸泪,当然最后完成的时候,也是颇有成就感的。

jizhongzuobiao(p,pos)子弹击中墙壁的下标计算
传入p 1-4,分别代表子弹从哪个方向击中墙壁;
传入pos,一个数组,存有子弹头的定位位置


function jizhongzuobiao(p,pos){//子弹击中砖墙的下标,传入子弹头位置和方向
    var eq=[];//定义一个空数组,用来装下标
    switch(p){//switch-case方法,写4个方向的击中脚本
        case 1://子弹向上击中墙壁
            var $x=Math.ceil(pos[0]/50)-1;
            var $y=Math.ceil(pos[1]/50)-1;//定义计算子弹所在和行列
            if(pos[0]%50==0){//判断是否打在中缝上,只有一种情况,子弹打击在中缝上,就是子弹头的坐标是50的倍数,因为我们已经圆整过坦克的坐标是5的倍数。
                var t1=17*$y+$x;
                var t2=17*$y+$x+1;//子弹打击在中缝上时,算击中两个墙
                if($('.map_i').eq(t1).hasClass('zhuan_qiang')
                    &&$('.map_i').eq(t1).position().top+$('.map_i').eq(t1).height()==pos[1]){//分别判断子弹击中的墙壁是不是砖墙,并且击中的墙壁的实际边缘是不是碰到子弹头,因为有的墙壁已经被打成25了,比如说上面图片中,子弹左边的墙高度25,子弹实际上被并有击中这个砖墙;若条件都符合,将下标传入数组eq中;
                    eq.push(t1);
                }
                if($('.map_i').eq(t2).hasClass('zhuan_qiang')
                    &&$('.map_i').eq(t2).position().top+$('.map_i').eq(t2).height()==pos[1]){
                    eq.push(t2);
                }
            }
            else{//没有击中中缝,意味着坦克子弹,只击中了一个墙
                var t1=17*$y+$x;//计算这个下标
                if($('.map_i').eq(t1).hasClass('zhuan_qiang')
                    &&$('.map_i').eq(t1).position().top+$('.map_i').eq(t1).height()==pos[1]){
                    eq.push(t1);
                }//再做一个判定,负荷传入数组
            }
            break;
        case 2://子弹向下击中墙壁,原理同上,计算过程有细微区别
            var $x=Math.ceil(pos[0]/50)-1;
            var $y=Math.floor((pos[1]+19)/50);
            if(pos[0]%50==0){
                var t1=17*$y+$x;
                var t2=17*$y+$x+1;
                if($('.map_i').eq(t1).hasClass('zhuan_qiang')
                    &&$('.map_i').eq(t1).position().top-19==pos[1]){
                    eq.push(t1);
                }
                if($('.map_i').eq(t2).hasClass('zhuan_qiang')
                    &&$('.map_i').eq(t2).position().top-19==pos[1]){
                    eq.push(t2);
                }
            }
            else{
                var t1=17*$y+$x;
                if($('.map_i').eq(t1).hasClass('zhuan_qiang')
                    &&$('.map_i').eq(t1).position().top-19==pos[1]){
                    eq.push(t1);
                }
            }
            break;
        case 3://子弹向左击中墙壁
            var $x=Math.ceil(pos[0]/50)-1;
            var $y=Math.ceil(pos[1]/50)-1;
            if(pos[1]%50==0){
                var t1=17*$y+$x;
                var t2=17*($y+1)+$x;
                if($('.map_i').eq(t1).hasClass('zhuan_qiang')
                    &&$('.map_i').eq(t1).position().left+$('.map_i').eq(t1).width()==pos[0]){
                    eq.push(t1);
                }
                if($('.map_i').eq(t2).hasClass('zhuan_qiang')
                    &&$('.map_i').eq(t2).position().left+$('.map_i').eq(t2).width()==pos[0]){
                    eq.push(t2);
                }
            }
            else{
                var t1=17*$y+$x;
                if($('.map_i').eq(t1).hasClass('zhuan_qiang')
                    &&$('.map_i').eq(t1).position().left+$('.map_i').eq(t1).width()==pos[0]){
                    eq.push(t1);
                }
            }
            break;
        case 4://子弹向右击中墙壁
            var $x=Math.floor(pos[0]/50);
            var $y=Math.ceil(pos[1]/50)-1;
            if(pos[1]%50==0){
                var t1=17*$y+$x;
                var t2=17*($y+1)+$x;
                if($('.map_i').eq(t1).hasClass('zhuan_qiang')
                    &&$('.map_i').eq(t1).position().left==pos[0]){
                    eq.push(t1);
                }
                if($('.map_i').eq(t2).hasClass('zhuan_qiang')
                    &&$('.map_i').eq(t2).position().left==pos[0]){
                    eq.push(t2);
                }
            }
            else{
                var t1=17*$y+$x;
                if($('.map_i').eq(t1).hasClass('zhuan_qiang')
                    &&$('.map_i').eq(t1).position().left==pos[0]){
                    eq.push(t1);
                }
            }
            break;
    }
    return eq;//将下标数组返回
}
这个脚本也很臃肿啊,所以写到这里我就不想写了,性能上已经有了缺陷。

图片描述
好了,亲爱的小伙伴们,阉割版坦克大战到此为止了,如果大家喜欢,可以关注我的慕课网帐号。我以后会不定期发表一些学习过程中的心得手记,共同学习,共同进步!

打开App,阅读手记
37人推荐
发表评论
随时随地看视频慕课网APP

热门评论

哈哈哈 活捉一只胖头鱼

胖头鱼是什么鬼,哈哈哈

表示看不懂,你已经很厉害了

查看全部评论