HTML5吃豆豆游戏开发实战(四)2d碰撞检测、重构-第二篇

           今天下午在家没事,写代码。先总结一下学习HTML5和JS的一些经验,再来说游戏的事吧!

        这完全是一个HTML5和JS的入门新手的见解,如果您和我一样是新手的话,欢迎交流,当然,高手如果不介意的话,帮小弟指点一二那就更好啦,谢谢,嘿嘿!入正题吧!

        1.语法方面

            1.1  JS关于数组的定义方法要注意:

                  比如:var walls = [new Wall(262,200,100,30),new Wall(662,60,30,400),new Wall(762,300,200,30)];

                  这是定义一个墙壁对象数组,基本元素是Wall对象,然后是构造函数(坐标,长宽)

                  更多的关于JS数组定义的可以看这里:JS定义数组以及初始化

            1.2  JS定义了一个类,在类的成员变量初始化的时候,似乎不可以调用成员函数来初始化它??

                   比如:

                  

function Wall(x_,y_,width_,height_)
{
    this.x = x_;
    this.y = y_;
    this.width = width_;
    this.height = height_;
    this.test = this.M();
    /**
     * @return {number}
     */
    this.M = function()
    {
        alert("....");
        return 1;
    };
.....(只是部分代码)

这样就不会看到alert效果,怎么回事呢?恳请各位指点下我初学者。

               1.3 注意我写的代码中的注释:

               

 this.hero = hero_;        //玩家
    this.walls = walls_;      //阻碍物数组
    this.enemys = enemy_;     //敌人数组
    this.isFind = false;      //临时变量,是否找到
    this.tempWall = null;     //临时变量
   // alert("cc");
    this.walls_y = this.walls.slice();  //这里不要写walls_,因为写这个的话,就和this.walls指向一个地方,排序后,walls也变了,终于找到原因,所以我用了复制

             函数传参的时候,若传的是对象,则是引用传递,如果是传数值类型,则是值传递。

           2. 一点点感想,JS是弱类型语言,var其实用着开始还觉得不习惯,因为以前一直写C,C++,JAVA,C#之类的强类型语言的原因吧,但是后来还是感觉很方便的,因为定义一个变量就是var,数值类型不用自己去考虑,但是这样也会有它的缺点,因为可能存在数据类型的使用是否正确,有没有重复定义、相互覆盖之类的,程序运行前的正确性判断基本靠人了,出错的可能性比较大,这里要注意,尤其是变量名的重复要注意!

          3.关于重构的一点点初步认识

        好的结构是后面更大的发展的基础,如果架子都不稳,怎么建高楼,所以我觉得重构确实还是很有必要的,尤其是稍微大一点点的,层次结构又模糊的项目,如果需要继续写下去,就最好重构一下。

       我个人认为重构的目的就是理清该项目的脉络,使各块代码到正确的位置上,而不是“你中有我,我中有你”

这种混乱不堪的局面,嘿嘿,比喻比喻,把代码模块儿理清楚,哪一部分该做什么,这样自己看得清楚,别人也看得清楚,同时为以后的扩展也打下基础。一个好的结构是很有必要的。

         好了,总结完毕,回到游戏中来。

         这是目前的工程文件目录截图:

        

        技术分享

             
        然后说一下完成的碰撞检测吧,其实核心就是坐标的比较,图形都很规范,所以不涉及复杂计算。

        当然,具体说一下,当玩家在同一个方向前进时,找到离自己最近的障碍物wallTemp,这就是继续在该方向行走下去一定会碰到的,只寻找一次,找到了把isFind=true。如果改变方向,则重新寻找wallTemp。玩家在某方向移动时,则比较自己和wallTemp之间的距离即可。上下左右都是这样。

        全部代码:

        1.

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>物体移动简单演示</title>
</head>
<!--注意在body这里添加按键响应事件,引号内为函数名-->
<body onkeydown="onGetKey()">
<h1>小球上下移动</h1>
<canvas id="test" width="1200px" height="600px" style="background-color: white"></canvas>
<!--把引用的资源引入本页面-->
<script type="text/javascript" src="Ball.js"></script>
<script type="text/javascript" src="Wall.js"></script>
<script type="text/javascript" src="Collide.js"></script>
<script type="text/javascript" src="GameSys.js"></script>
<script type="text/javascript" src="Control.js"></script>
<script type="text/javascript">
    //1.定义游戏世界原点坐标 窗口宽高
    var Ox = 162;
    var Oy = 0;
    var WinWidth = 900;
    var WinHeight = 500;
    //2.实例化游戏管理类
    var mana = new GameSys(Ox,Oy,WinWidth,WinHeight);
    //3.获取画布
    var can = document.getElementById("test");
    var cxt = can.getContext("2d");
    //4.实例化小球对象
    var ball = new Ball(30 + mana.ox,30,20,3);
    //walls先按x从小到大排序
    //5.
    var walls = [new Wall(262,200,100,30),new Wall(662,60,30,400),new Wall(762,300,200,30)];
    //6.
    FreshWindow(162,0,900,500,walls);
    //7.玩家控制类
    var ctrl = new Control(ball,walls,null);
    //8.设置小球的动画循环
    setInterval("ball.drawBall()",100);

    function FreshWindow(ox,oy,winWidth,winHeight,walls_)       //参数代表地图上的静态景物
    {
        cxt.clearRect(ox,oy,winWidth,winHeight);    //清理画布
        cxt.strokeRect(ox,oy,winWidth,winHeight);
        //document.write("xx");
        for(var i=0;i<walls_.length;i++)
        {
            cxt.strokeRect(walls_[i].x,walls_[i].y,walls_[i].width,walls_[i].height);
        }
       // alert("alter");
    }
    function onGetKey()
    {
        ctrl.moveCtrl();

    }
</script>
</body>
</html>

        2.

//玩家操控类,封装了W,A,S,D等等操作
function Control(hero_,walls_,enemies_)
{
    this.keyTemp = 0;//按键寄存器,存放上一次的按键值
    this.ball = hero_;
    this.wall = walls_;
    this.enemy = enemies_;
    this.collide = new Collide(hero_,walls_,enemies_);
    this.moveCtrl = function()
    {
        var code = event.keyCode;//对应字母的ascii
        //如果上一次的按键和这一次的不相等
        if(this.keyTemp != code)
        {
            this.keyTemp = code;         //更新按键值
            this.collide.ColideReferenceClean();  //修改了方向,清空上一次的计算值(待碰撞物体),重新的方向计算新的待碰撞物体
        }
        switch(code)
        {
            //数字是ASCII码,记不住的话可以对照ASCII码表
            //上
            case 87:
                this.ball.dir=0;
                // document.write("c");
                //if(this.ball.getMinY() >= mana.topY)
                if( !this.collide.IsCollide() && this.ball.getMinY() >= mana.topY)
                    this.ball.moveUp();
                break;
            //下
            case 83:
                this.ball.dir=1;
                //if(this.ball.getMaxY() <= mana.buttomY)
                if( !this.collide.IsCollide() && this.ball.getMaxY() <= mana.buttomY)
                    this.ball.moveDown();
                break;
            //左
            case 65:
                this.ball.dir=2;
                if( !this.collide.IsCollide() && this.ball.getMinX() >= mana.leftEgdeX)
                    this.ball.moveLeft();
                break;
            //右
            case 68:
                this.ball.dir=3;
                if( !this.collide.IsCollide() && this.ball.getMaxX() <= mana.rightEdgeX)
                    this.ball.moveRight();
                break;
            default :break;
        }
    };
}

3.

//小球类
//注意js里面类的定义方法,直接类加函数参数的形式,就相当于定义了,然后里面直接this.xx=xx_,很高效
//参数:x,y坐标,球的方向,球的半径,运动速度
function Ball(x_,y_,r_,sp_)
{

    this.x = x_;
    this.y = y_;
    this.dir = 0;
    this.r = r_;    //灰色表示还没用
    this.sp = sp_;
    this.state = 0;
    //定义上下左右:0,1,2,3
    this.moveUp = function()
    {
        this.y -= this.sp;//上
        this.dir = 0;
    };
    this.moveDown = function()
    {
        this.y += this.sp;//下
        this.dir = 1;
    };
    this.moveLeft = function()
    {
        this.x -= this.sp;//左
        this.dir = 2;
    };
    this.moveRight = function()
    {
        this.x += this.sp;//右
        this.dir = 3;
    };
    //获得小球的坐标
    this.getX = function()
    {
        return this.x;
    };
    this.getY = function()
    {
        return this.y;
    };
    //获得球的各个方向的边界值
    this.getMaxX = function() {
        return this.x + this.r;
    };
    this.getMaxY = function()
    {
        return this.y + this.r;
    };
    /**
     * @return {number}
     */
    this.getMinX = function()
    {
        return this.x - this.r;
    };
    /**
     * @return {number}
     */
    this.getMinY = function()
    {
        return this.y - this.r;
    };
    this.getDir = function()
    {
        return this.dir;
    };
    this.drawBall = function()
    {
        FreshWindow(Ox,Oy,WinWidth,WinHeight,walls);
        switch (this.dir)
        {
            case 0:
                this.drawBall_UpOrDown(true);
                break;
            case 1:
                this.drawBall_UpOrDown(false);
                break;
            case 2:
                this.drawBall_RightOrLeft(false);
                break;
            case  3:
                this.drawBall_RightOrLeft(true);
                break;
            default :
                break;
        }
    };
    this.drawBall_RightOrLeft = function(isRight)
    {
        //document.write(state);
        //画眼睛,眼睛是公共的
        //画眼睛-外圈
        var eyeX;
        if(isRight == true)     //右
            eyeX = this.x - 5;
        else eyeX = this.x + 5;//左
        var eyeY = this.y-8;
        var eyeR = 6;//目前限定死这个
        cxt.beginPath();
        cxt.fillStyle="#000000";
        cxt.arc(eyeX,eyeY,eyeR,0,Math.PI * 2,false);
        cxt.fill();
        cxt.closePath();
        //画眼睛-眼球
        var qiuR = eyeR / 2;
        cxt.beginPath();
        cxt.fillStyle="#FF0000";
        cxt.arc(eyeX,eyeY,qiuR,0,Math.PI * 2,false);
        cxt.fill();
        cxt.closePath();
        switch(this.state)
        {
            //张嘴
            case 1:
                //画红球
                cxt.beginPath();
                cxt.fillStyle="#FF0000";
                //嘴巴大小为90°
                //画圆弧--脸
                if(isRight)
                    cxt.arc(this.x,this.y,this.r,1/4 * Math.PI,3/2 * Math.PI + 1/4 * Math.PI,false);
                else
                    cxt.arc(this.x,this.y,this.r,3/4 * Math.PI, Math.PI + 1/4 * Math.PI,true);
                cxt.stroke();
                cxt.closePath();
                cxt.beginPath();
                //画嘴巴
                var ax = 0,ay = 0;
                var bx = 0,by = 0;
                var temp = this.r * Math.sqrt(2)/2;
                if(isRight)
                    ax = this.x + temp;
                else
                    ax = this.x - temp;
                ay = this.y - temp;
                bx = ax;
                by = this.y + temp;
                cxt.moveTo(this.x,this.y);
                cxt.lineTo(ax,ay);
                cxt.moveTo(this.x,this.y);
                cxt.lineTo(bx,by);
                cxt.closePath();
                cxt.stroke();
                this.state = 0;
                break;
            //闭嘴
            case 0:
                //画圆弧--脸
                cxt.beginPath();
                cxt.arc(this.x,this.y,this.r,0,Math.PI * 2,false);
                cxt.stroke();
                cxt.closePath();
                //从圆心到嘴巴末点的连线
                cxt.beginPath();
                cxt.moveTo(this.x,this.y);
                if(isRight)
                    cxt.lineTo(this.x + this.r,this.y);
                else
                    cxt.lineTo(this.x - this.r,this.y);
                cxt.stroke();
                cxt.closePath();
                this.state = 1;
                break;
            default :
                break;
        }
    };
    this.drawBall_UpOrDown = function(isUp)
    {
        //document.write(state);
        //画眼睛,眼睛是公共的
        //画眼睛-外圈
        var eyeX = this.x - 5;
        var eyeY = this.y + 8;
        if(!isUp)
        {
            eyeX = this.x + 5;
            eyeY = this.y - 8;
        }

        var eyeR = 6;//目前限定死这个
        cxt.beginPath();
        cxt.fillStyle="#000000";
        cxt.arc(eyeX,eyeY,eyeR,0,Math.PI * 2,false);
        cxt.fill();
        cxt.closePath();
        //画眼睛-眼球
        var qiuR = eyeR / 2;
        cxt.beginPath();
        cxt.fillStyle="#FF0000";
        cxt.arc(eyeX,eyeY,qiuR,0,Math.PI * 2,false);
        cxt.fill();
        cxt.closePath();
        switch(this.state)
        {
            //张嘴
            case 1:
                //画红球
                cxt.beginPath();
                cxt.fillStyle="#FF0000";
                //嘴巴大小为90°
                //画圆弧--脸
                if(!isUp)
                    cxt.arc(this.x,this.y,this.r,1/4 * Math.PI ,3/4 * Math.PI,true);
                else
                    cxt.arc(this.x,this.y,this.r,Math.PI +  1/4 * Math.PI,3/2 * Math.PI+  1/4 * Math.PI,true);
                cxt.stroke();
                cxt.closePath();
                cxt.beginPath();
                //画嘴巴
                var ax = 0,ay = 0;
                var bx = 0,by = 0;
                var temp = this.r * Math.sqrt(2)/2;
                ax = this.x - temp;
                ay = this.y - temp;
                by = ay;
                bx = this.x + temp;
                if(!isUp)
                {
                    ax = this.x + temp;
                    ay = this.y + temp;
                    by = ay;
                    bx = this.x - temp;
                }
                cxt.moveTo(this.x,this.y);
                cxt.lineTo(ax,ay);
                cxt.moveTo(this.x,this.y);
                cxt.lineTo(bx,by);
                cxt.closePath();
                cxt.stroke();
                this.state = 0;
                break;
            //闭嘴
            case 0:
                //画圆弧--脸
                cxt.beginPath();
                cxt.arc(this.x,this.y,this.r,0,Math.PI * 2,false);
                cxt.stroke();
                cxt.closePath();
                //从圆心到嘴巴末点的连线
                cxt.beginPath();
                cxt.moveTo(this.x,this.y);
                if(!isUp)
                    cxt.lineTo(this.x ,this.y + this.r);
                else
                    cxt.lineTo(this.x ,this.y- this.r);
                cxt.stroke();
                cxt.closePath();
                this.state = 1;
                break;
            default :
                break;
        }

    };
}

4.

//2d碰撞检测引擎
//参数:玩家,阻碍物数组,敌人数组
function Collide(hero_,walls_,enemy_)
{
    this.hero = hero_;        //玩家
    this.walls = walls_;      //阻碍物数组
    this.enemys = enemy_;     //敌人数组
    this.isFind = false;      //临时变量,是否找到
    this.tempWall = null;     //临时变量
   // alert("cc");
    this.walls_y = this.walls.slice();  //这里不要写walls_,因为写这个的话,就和this.walls指向一个地方,排序后,walls也变了,终于找到原因,所以我用了复制
    this.isSort = false;      //数组是否排序。好像不能调用类中的函数,调用了没反应。。。。

    alert(this.walls);
    /**
     * @return {boolean}
     */
    //检测是否碰到,返回true,碰到,else没碰到
    this.IsCollide = function()
    {
        //是否找到离自己最近的墙壁

        //如果没有找到,则寻找

        switch (this.hero.getDir())
        {

            //上:
            case 0:
                if(this.tempWall == null)
                {
                    //     alert("wall_已被清空");
                    this.tempWall = this.getUpNestWall();
                }
                else
                {
                    if(this.hero.getMinY() <= this.tempWall.getWY()+this.tempWall.getHeight())
                    {

                      //  alert(this.tempWall.getWX());
                        return true;
                    }

                }
                break;
            //下
            case 1:
                if(this.tempWall == null)
                {
                    //     alert("wall_已被清空");
                    this.tempWall = this.getButtomNestWall();
                }
                else
                {
                    if(this.hero.getMaxY() >= this.tempWall.getWY())
                    {

                      //  alert(this.tempWall.getWX());
                        return true;
                    }

                }
                break;
            //左
            case 2:
                // alert("按左");//因为按下左的瞬间,球是先判断,并不是先转向,所以此时的方向还是上一个方向,这样便造成了错误,逻辑bug,应该按下后先转向,再判断该方向的障碍

                if(this.tempWall == null)
                {
                    //     alert("wall_已被清空");
                    this.tempWall = this.getLeftNestWall();
                }
                else
                {
                    if(this.hero.getMinX() <= this.tempWall.getWX()+this.tempWall.getWidth())
                    {

                        alert(this.tempWall.getWX());
                        return true;
                    }

                }
                break;
            //右
            case 3:
                //alert(this.hero.getMaxX());
                if(this.tempWall == null)
                {
                    //alert("NULL");
                    this.tempWall = this.getRightNestWall();
                }
                else
                {

                    //alert("墙壁的是:");
                    return this.hero.getMaxX() >= this.tempWall.getWX();
                }
                break;
            default : return false;
                break;

        }
    };
    //获得向右方向离自己最近的墙壁
    this.getRightNestWall = function ()
    {
        var y;
        y = 0;
        var temp = 0;
        //往右
        y = this.hero.getY();
        var i;
        var wallTemp = null;
        if(!this.isFind)
        {
                for(i = 0;i<this.walls.length;i++)
                {
                    temp = this.walls[i].getWY();
                    if(y >= temp && y<=temp + this.walls[i].height)
                    {
                        if(this.hero.getMaxX() <= this.walls[i].getWX())
                        {
                            wallTemp = this.walls[i];
                           // alert("......find");
                            alert("i="+i+"x="+wallTemp.getWX()+"y="+wallTemp.getWY()+"width="+wallTemp.getWidth()+"height="+wallTemp.getHeight());
                            this.isFind = true;      //找到
                           // alert("i="+i+"y="+wallTemp.getWY()+"height="+wallTemp.getHeight()+"temp="+temp+"dir="+ball.getDir()+"isRight="+isRight);
                            return wallTemp;//已经找到自己前进方向上离自己最近的墙壁,不再寻找,跳出循环
                        }
                    }

                }


        }
    };
    //获得左边离自己最近的墙
    this.getLeftNestWall = function()
    {
        var y;
        y = 0;
        var temp = 0;
        //往左
        y = this.hero.getY();
        var i;
        var wallTemp = null;
        var end = this.walls.length - 1;
        if(!this.isFind)
        {
            for(i = end;i>=0;i--)
            {
                temp = this.walls[i].getWY();
                if(y >= temp && y<=temp + this.walls[i].height)
                {
                    if(this.hero.getMinX() >= this.walls[i].getWX() + this.walls[i].getWidth() )
                    {

                        wallTemp = this.walls[i];
                        // alert("i="+i+"y="+wallTemp.getWY()+"height="+wallTemp.getHeight()+"temp="+temp+"dir="+ball.getDir()+"isRight="+isRight);


                        this.isFind = true;      //找到
                        //alert("i="+i+"y="+wallTemp.getWY()+"height="+wallTemp.getHeight()+"temp="+temp+"dir="+ball.getDir()+"isRight="+isRight);
                        return wallTemp;//已经找到自己前进方向上离自己最近的墙壁,不再寻找,跳出循环
                    }
                }

            }


        }
    };
    //获得上边离自己最近的墙
    this.getUpNestWall = function()
    {
        //如果没有排序,先排序
        if(this.isSort == false)
        {
            this.sortBy_y(this.walls_y);
            this.isSort = true;
            //alert(this.walls_y);
        }
        else
        {
            var x = 0;
            var temp = 0;
            //往右
            x = this.hero.getX();
            var i;
            var wallTemp = null;
            var end = this.walls_y.length - 1;
            if(!this.isFind)
            {
                for(i = end;i >= 0;i--)
                {
                    temp = this.walls_y[i].getWX();
                    if(x >= temp && x<=temp + this.walls_y[i].width)
                    {
                        if(this.hero.getMinY() >= this.walls_y[i].getWY() + this.walls_y[i].height)
                        {
                            wallTemp = this.walls_y[i];
                            // alert("i="+i+"y="+wallTemp.getWY()+"height="+wallTemp.getHeight()+"temp="+temp+"dir="+ball.getDir()+"isRight="+isRight);
                            this.isFind = true;      //找到
                            //alert("i="+i+"y="+wallTemp.getWY()+"height="+wallTemp.getHeight()+"temp="+temp+"dir="+ball.getDir()+"isRight="+isRight);
                            return wallTemp;//已经找到自己前进方向上离自己最近的墙壁,不再寻找,跳出循环
                        }
                    }

                }


            }
        }
    };
    this.getButtomNestWall = function()
    {
        //如果没有排序,先排序
        if(this.isSort == false)
        {
            this.sortBy_y(this.walls_y);
            this.isSort = true;
            //alert(this.walls_y);
        }
        else
        {
            var x = 0;
            var temp = 0;
            //往右
            x = this.hero.getX();
            var i;
            var wallTemp = null;
            if(!this.isFind)
            {
                for(i = 0;i<this.walls_y.length;i++)
                {
                    temp = this.walls_y[i].getWX();
                    if(x >= temp && x<=temp + this.walls_y[i].width)
                    {
                        if(this.hero.getMaxY() <= this.walls_y[i].getWY())
                        {
                            wallTemp = this.walls_y[i];
                            // alert("i="+i+"y="+wallTemp.getWY()+"height="+wallTemp.getHeight()+"temp="+temp+"dir="+ball.getDir()+"isRight="+isRight);
                            this.isFind = true;      //找到
                            //alert("i="+i+"y="+wallTemp.getWY()+"height="+wallTemp.getHeight()+"temp="+temp+"dir="+ball.getDir()+"isRight="+isRight);
                            return wallTemp;//已经找到自己前进方向上离自己最近的墙壁,不再寻找,跳出循环
                        }
                    }

                }


            }
        }
    };
    //排序,根据y来排,由小到大
    //冒泡排序算法
    this.sortBy_y = function(arr)
    {
       // alert("endxxxxx");
        var temp = null;
        var l =  arr.length;
        //alert("length="+l);
        for(var i = 0;i < l-1;i++)
        {
            for(var j = 0;j<l -1- i;j++)
            {

                if(arr[j].getWY() >= arr[j + 1].getWY())
                {
                    //交换
                    temp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = temp;
                }

            }
        }


    };
    this.ColideReferenceClean = function()
    {
        this.isFind=false;
        //存放离自己最近的、如果继续直线前进不转向将会碰撞的wall
        this.tempWall=null;
       // alert("clean");
    };
}


5.

//游戏系统管理类

function GameSys(ox_,oy_,winWidth_,winHeight_)
{
   // this.can = document.getElementById(id_);//画布id
   // this.cxt = can.getContext("2d");
    this.ox = ox_;                      //游戏窗口的坐标原点
    this.oy = oy_;
    this.winWidth = winWidth_;          //窗口宽高
    this.winHeight = winHeight_;
    this.rightEdgeX = ox_ + winWidth_;  //四周边界值
    this.leftEgdeX =  ox_;
    this.topY =   oy_;
    this.buttomY = oy_ + winHeight_;
    this.walls = null;                  //地图上的静态景物,不会多,也不会少的物体,是一个数组

    document.write("xx");


}

6.

function Wall(x_,y_,width_,height_)
{
    this.x = x_;
    this.y = y_;
    this.width = width_;
    this.height = height_;
    this.getWX = function()
    {
        return this.x;
    };
    this.getWY = function()
    {
        return this.y;
    };
    this.getWidth = function()
    {
        return this.width;
    };
    this.getHeight = function()
    {
        return this.height;
    };

}



游戏运行截图:

技术分享



                 

          

        

一步步来完善吧!加油。。。



郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。