js canvas游戏初级demo-躲避障碍物

本文详细介绍了使用JavaScript和Canvas API开发简单游戏的过程,包括角色移动、矩形下落及碰撞检测等功能实现。通过监听键盘事件控制角色移动,利用定时器更新游戏状态,实现了基本的游戏逻辑。

在线演示地址 http://200ok.fun:3100/html/game_demo.html

继上次js canvas游戏初级demo-上下左右移动(https://www.cnblogs.com/lzs-888/p/7427440.html),之后,对其新加了矩形下落和碰撞检测功能

1.头像移动
设置两个按键监听事件(keydown - 按下,keyup - 松开),每当按下时就将按下的键的state改为true,松开则相反,
然后,设置了一个定时器,每一定时间会读取这些按键state,根据state和速度进行头像位置坐标的调整,并且擦除头像,重新画上

2.矩形下落
与头像移动类似,只是擦除和绘制有多个(本代码中用设置多个定时器来维护这些矩形的绘制),并且横坐标和下落速度在一定范围内随机

3.碰撞检测
这个直接看代码,本代码写的有点啰嗦

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>按键盘的上下左右躲开这些黑色障碍物</title>
</head>
<style>
    #canvas_dom{border: 1px solid #000;margin: 0 auto;display: block;background: #fff;}
    .title{text-align: center;}
    #game_time{text-align: center;}
</style>
<body>
    <h1 class="title">按键盘的上下左右躲开这些黑色障碍物</h1>
    <h2 id="game_time">你坚持的时间:<span>0</span>S</h2>
    <canvas id="canvas_dom"></canvas>
    <script>
        /*
            ddmm
            2018/07/13

            1.头像移动
            设置两个按键监听事件(keydown - 按下,keyup - 松开),每当按下时就将按下的键的state改为true,松开则相反,
            然后,设置了一个定时器,每一定时间会读取这些按键state,根据state和速度进行头像位置坐标的调整,并且擦除头像,重新画上

            2.矩形下落
            与头像移动类似,只是擦除和绘制有多个(本代码中用设置多个定时器来维护这些矩形的绘制),并且横坐标和下落速度在一定范围内随机

            3.碰撞检测
            这个直接看代码,本代码写的有点啰嗦
         */
        //全局变量
        var g = {

            ctx: null,//画布会话上下文
               itv_ids: [],//用于记录定时器id,便于一起clear掉,减少内存消耗
               game_over: false,//游戏是否结束
               refreshNumForSec: 60,//每秒画面刷新次数
               game_time: 0, //游戏坚持的时间
            canvasWidth: 1000,//画布宽度
            canvasHeight: 700,//画布高度
            faceDataUrl: 'http://www.200ok.fun:3100/images/wudier.png',//头像地址
            img: null,//头像图片引用
            faceWidth: 80,
            faceHeight: 82.5,
            faceX: 0,//头像位置x坐标(其初始值会在首次加载被initFacePos方法计算得出,故设置是无效的)
            faceY: 0,//头像位置y坐标(其初始值会在首次加载被initFacePos方法计算得出,故设置是无效的)
            faceLastX: 0,//头像上一次位置x坐标,用于擦除(其初始值会在首次加载被initFacePos方法计算得出,故设置是无效的)
            faceLastY: 0,//头像上一次位置y坐标,用于擦除(其初始值会在首次加载被initFacePos方法计算得出,故设置是无效的)
            keyRight: false,//是否按了→
            keyLeft: false,//是否按了←
            keyUp: false,//是否按了↑
            keyDown: false,//是否按了↓
            faceSpeed: 15,//头像每次刷新画布移动的像素
               rectW: 200,//矩形的宽度
            rectH: 20,//矩形的高度
               rectSpeedMin: 3,//方块速度最小值
               rectSpeedMax: 15,//方块速度最大值
               rectFlowNumForSec: 1,//每秒生成矩形个数
        };
 
        _main();//主函数
 
        //获取画布上下文
        function getCtx(){
            if(!g.ctx){
                //获取dom
                var canvas = document.querySelector('#canvas_dom');
                
                //设置宽高
                canvas.width = g.canvasWidth;
                canvas.height = g.canvasHeight;
                
                //获取会话上下文
                g.ctx = canvas.getContext('2d');
            }
            
            return g.ctx;
        }
 
        function _main(){
            getCtx();//获取画布上下文

            //获取头像图片
            g.img = new Image();
            g.img.src = g.faceDataUrl;
            g.img.width = g.faceWidth;
            g.img.height = g.faceHeight;
            g.img.onload = function(){//图片加载完成
                g.img.style.border = "1px solid #000";
                initFacePos();
            }
             
             //监听keydown事件
            window.addEventListener('keydown',function(e){
                var k = e.key;
                stateJudge(k,true);//修改按键state
            });
         
             //监听keyup事件
            window.addEventListener('keyup',function(e){
                var k = e.key;
                stateJudge(k,false);//修改按键state
            });
             
             //设置定时器,1秒重绘 g.refreshNumForSec 次头像
            g.itv_ids.push(
                setInterval(function(){
                    if(!g.game_over){
                        moveJudge();
                        //擦除上一次画的
                        g.ctx.clearRect(g.faceLastX,g.faceLastY,g.img.width,g.img.height);
                        //绘制之前保存一下位置,用于下次擦除
                        g.faceLastX = g.faceX;
                        g.faceLastY = g.faceY;
                        //绘制头像
                        g.ctx.drawImage(g.img,g.faceX,g.faceY,g.img.width,g.img.height);
                    }
                },1000/g.refreshNumForSec)
            );

            //每1秒生成一个随机位置,随机速度的矩形往下掉
            g.itv_ids.push(
                setInterval(function(){
                    var max = g.canvasWidth - g.rectW;//画布宽度 - 矩形宽度
                    var x = get_random_num(0,max);//获取x坐标随机值
                    var speed = get_random_num(g.rectSpeedMin,g.rectSpeedMax);//获取速度随机值
                       drawTheRect(x,0,g.rectW,g.rectH,speed);
                },1000/g.rectFlowNumForSec)
            );

            //数秒,改dom
            g.itv_ids.push(
                setInterval(function(){
                    g.game_time++;
                    document.querySelector('#game_time span').innerText = g.game_time;
                },1000)
            );
        }

        //根据画布和头像宽高,初始化头像位置,将其画于底部中间位置
        function initFacePos(){
            g.faceX = g.canvasWidth/2 - g.img.width/2;
            g.faceY = g.canvasHeight - g.img.height;
            g.faceLastX = g.faceX;
            g.faceLastY = g.faceY;
            g.ctx.drawImage(g.img,g.faceX,g.faceY,g.img.width,g.img.height);//绘制头像
        }

        /**
        * [checkRectImpact 碰撞检测,如果所提供的坐标点碰撞则返回true,否则返回false]
        * - - 没看过别人怎么写的,写的有点啰嗦
        * x1 x2 y1 y2 分别为第一个矩形(下称矩形A)的左边横坐标,右边横坐标,上边纵坐标,下边纵坐标
        * a1 a2 b1 b2 分别为第二个矩形(下称矩形B)的左边横坐标,右边横坐标,上边纵坐标,下边纵坐标
        * 坐标点如下图所示
          x1      x2 
            ————————   y1
            |       |
            |       |
            |        |
            ————————   y2

            a1      a2 
            ————————   b1
            |       |
            |       |
            |        |
            ————————   b2
        */
        function checkRectImpact(x1,x2,y1,y2,a1,a2,b1,b2){
            //分别为 左上角碰到 || 右上角碰到 || 右下角碰到 || 左下角碰到 || 矩形B大于矩形A且矩形B从下至上包含矩形A穿过 || 矩形B大于矩形A且矩形B从上至下包含矩形A穿过 || 矩形A大于矩形B且矩形A从下至上包含矩形B穿过 || 矩形A大于矩形B且矩形A从上至下包含矩形B穿过
            return (x1 <= a1 && x2 >= a1 && y1 <= b1 && y2 >= b1) || (x1 <= a2 && x2 >= a2 && y1 <= b1 && y2 >= b1) || (x1 <= a2 && x2 >= a2 && y1 <= b2 && y2 >= b2) || (x1 <= a1 && x2 >= a1 && y1 <= b2 && y2 >= b2) || (x1 >= a1 && x2 <= a2 && y2 >= b1 && y1 <= b1) || (x1 >= a1 && x2 <= a2 && y2 >= b2 && y1 <= b2) || (a1 >= x1 && a2 <= x2 && b2 >= y1 && b1 <= y1) || (a1 >= x1 && a2 <= x2 && b2 >= y2 && b1 <= y2);
        }

        //获取某个某个区间内的随机整数 ,获取到的值域为[min,max)
        function get_random_num(min,max){
            if(/^-?\d+$/.test(min) && /^-?\d+$/.test(max) && max>min){
                return parseInt(Math.random()*(max - min) + min);
            }else{
                return false;
            }
        }

        /**
         * [drawTheRect 画矩形]
         * @param  {[type]} x     [矩形距离左边框的距离]
         * @param  {[type]} y     [矩形距离上边框的距离]
         * @param  {[type]} w     [矩形的宽度]
         * @param  {[type]} h     [矩形的高度]
         * @param  {[type]} speed [每一次重绘画面,矩形下坠的距离,即下坠速度]
         * @return {[type]}       [description]
         */
        function drawTheRect(x,y,w,h,speed){
            var itv_id = setInterval(function(){
                if(!g.game_over){
                    //绘制之前保存一下位置,用于下次擦除
                    var lastX = x;
                    var lastY = y;
                    // var speed = get_random_num(3,120); //经G民同学提示,可以考虑每次重绘方块时给其变速
                    y += speed;
                    g.ctx.clearRect(lastX,lastY,w,h); //擦除
                    g.ctx.fillRect(x,y,w,h); //
                    if(checkRectImpact(g.faceX,g.faceX + g.img.width,g.faceY,g.faceY + g.img.height,x,x+w,y,y+h)){//碰撞检测
                        game_over();
                        return;
                    }
                    if(y > g.canvasHeight){//如果已经超过了画布的高度那么移除这个定时器,减少内存消耗
                        clearInterval(itv_id);
                    }
                }
            },1000/g.refreshNumForSec);
            g.itv_ids.push(itv_id);//存到全局变量
        }

        //游戏结束
        function game_over(){
            clearAllInterval();
            g.game_over = true;
            setTimeout(function(){//如果不延迟可能会导致上一次绘画未完成就被阻塞了
                if(confirm("游戏结束,你坚持了" + g.game_time + "S" + ",继续努力!\n重新开始游戏请点'是'")){
                    window.location.reload();
                }
            },50);
        }

        //clear所有定时器
        function clearAllInterval(){
            for(var i in g.itv_ids){
                r = g.itv_ids[i];
                clearInterval(r);
            }
        }
         
         //根据按键状态修改头像的坐标
        function moveJudge(){
            if(g.keyRight === true){
                g.faceX += g.faceSpeed;
            }else if(g.keyLeft === true){
                g.faceX -= g.faceSpeed;
            }else if(g.keyUp === true){
                g.faceY -= g.faceSpeed;
            }else if(g.keyDown === true){
                g.faceY += g.faceSpeed;
            }

            //边界情况处理,不要让头像超出边界
            if(g.faceX > g.canvasWidth - g.img.width){
                g.faceX = g.canvasWidth - g.img.width;
            }else if(g.faceX < 0){
                g.faceX = 0;
            }
            if(g.faceY > g.canvasHeight - g.img.height){
                g.faceY = g.canvasHeight - g.img.height;
            }else if(g.faceY < 0){
                g.faceY = 0;
            }
        }
 
        //根据按键修改状态
        function stateJudge(k,v){
            if(k == 'ArrowRight'){
                g.keyRight = v;
            }else if(k == 'ArrowLeft'){
                g.keyLeft = v;
            }else if(k == "ArrowUp"){
                g.keyUp = v;
            }else if(k == "ArrowDown"){
                g.keyDown = v;
            }
        }
    </script>
</body>
</html>

 

转载于:https://www.cnblogs.com/lzs-888/p/9317858.html

http://blog.youkuaiyun.com/xiaoxiao108/article/details/8913616 html5 挺火,写个html5游戏玩玩吧,想起cocos2d 貌似各个平台都有,网上找了找,下载了个Cocos2d-html5引擎包。 貌似另一个开源引擎lufylegend.js也很好,下次用用lufylegend.js试试。 开发环境 chrome Cocos2d-html5 游戏地址:http://hehe108.sinaapp.com/cocos2d/ (adsw回车) 实现方法如下 1.创建好 LayerGradient的子类 (里面放坦克子弹) 2.重写 onEnter 方法添加一些基本按钮 跟一些初始坦克,子弹 3.通过schedule方法 控制 坦克 子弹的重画 4.根据键盘按键(ASWD)确定出坦克的方向,根据坦克的方向修改坦克的X,Y轴坐标,来实现坦克的移动 5.通过cc.rectIntersectsRect函数来进行碰撞检测,实现子弹打击坦克 具体代码 1.在项目里面添加方向 var Direction = { L:0, U:1, D:2, R:3, STOP:4 }; 2.添加子弹类的相关属性 SPEED:10, WIDTH:15, HEIGHT:15, x:null, y:null, dir:null, live:true, tankClient:null, //LayerGradient子类TankClient 的引用 good:null, 3.子弹初始化,重画 ctor:function (x,y,good,dir,tankClient) { cc.associateWithNative( this, cc.Sprite ); this.x=x; this.y=y; this.dir=dir; this.tankClient=tankClient; this.good=good; this.initWithFile(s_missile); this.setPosition( cc.p(this.x, this.y) ); this.tankClient.addChild(this); }, Draw:function(){ if(!this.live){ this.tankClient.removeChild(this, true); return; } this.setPosition( cc.p(this.x, this.y) ); this.Move(); }, 4.添加子弹打击坦克的方法 HitTank:function(t){ if (cc.rectIntersectsRect(this.GetRectangle(), t.GetRectangle()) && t.live && this.live && this.good != t.good){ t.live = false; this.live = false; return true; } return false; }, 5.添加坦克类相关属性 SPEED:5, WIDTH:58, HEIGHT:58, x:0, y:0, l:false, u:false, r:false, d:false, dir:Direction["STOP"], ptDir:Direction["D"], tankClient:null, good:null, step:0, live:true, 6.在tank类中 坦克初始化,重画 ctor:function (x,y,good,tankClient) { cc.associateWithNative( this, cc.Sprite ); this.x=x; this.y=y; this.tankClient=tankClient; this.good=good; if(good){ this.initWithFile(s_tank); }else{ this.initWithFile(s_enemy); } this.setPosition( cc.p(this.x, this.y) ); this.tankClient.addChild(this); }, Draw:function(){ if (!this.live){ if (!this.good){ this.tankClient.removeChild(this, true); } this.tankClient.removeChild(this, true); return; } this.setPosition( cc.p(this.x, this.y) ); switch (this.ptDir) { case Direction["D"]: this.setRotation(0); //旋转精灵控制 炮筒方向 break; case Direction["U"]: this.setRotation(180); break; case Direction["L"]: this.setRotation(270); break; case Direction["R"]: this.setRotation(90); break; } this.Move(); }, 7.tank发子弹的方法 Fire:function() { if(!this.live) return null; for(var i=0;i<this.tankClient.missiles.length;i++){ var m = this.tankClient.missiles[i]; if (m.live == false){ m.x=this.x; m.y=this.y; m.live=true; m.dir=this.ptDir; m.good=this.good; this.tankClient.addChild(m); return m; } } var missile=new Missile(this.x,this.y,this.good,this.ptDir,this.tankClient); this.tankClient.missiles.push(missile); return missile; }, 8.LayerGradient加入坦克 this.tanks = []; this.myTank = new Tank(60,20, true, this); for (var i = 0; i < 10; i++){ this.tanks.push(new Tank(50 + 70 * (i + 1), 420, false, this)); } 9.LayerGradient中调用子弹打击坦克的方法 for(var i=0;i<this.missiles.length;i++){ var m = this.missiles[i]; m.HitTank(this.myTank); m.HitTanks(this.tanks); m.Draw(); } 10.控制坦克移动射击的部分代码 onKeyUp:function(key) { this.myTank.KeyReleased(key); }, onKeyDown:function(key) { this.myTank.KeyPressed(key); } 11.用Ant和compiler合并压缩js后发布到sae 如果你发现有什么不合理的,需要改进的地方,请留言。或者可以通过 328452421@qq.com 联系我,非常感谢。 http://blog.youkuaiyun.com/xiaoxiao108/article/details/8913616
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值