zoukankan      html  css  js  c++  java
  • 桌球

    用Canvas写桌球游戏!!!

     

      声明:本文为原创文章,如需转载,请注明来源WAxes,谢谢!

      昨天上班的时候闲着无事,就用Canvas写了个桌球游戏来玩玩。。。。所以就拿这游戏上来水一发。或许对一些刚学canvas的人有帮助。

      话说这只是一个简单的DEMO。游戏性,游戏规则什么的我都没怎么考虑,如果有兴趣细化的朋友可以细化一下,比如细化一下规则,游戏开关,加个声音,细化一下进球检测,更严谨甚至可以去查下击球力度、桌面真实摩擦力等来把游戏弄的更像游戏。我只是给个编程思路哈,随便坐个DEMO而已,玩起来估计还是不会很爽快的~~

        桌球游戏

      整个桌球游戏就两个类,一个是球,一个是辅助瞄准线。如果想把改游戏弄的更复杂,还可以再抽象一个形状类,用于检测球与边角的碰撞以及进球。我做的这个游戏采取了最简单的墙壁碰撞检测,所以没有进行球与不规则形状的碰撞检测,如果想玩更复杂的碰撞,可以戳 关于简单的碰撞检测 岑安大大讲的还是很好的。好,接下来就一步一步来:

      【球】

      先贴代码:

    复制代码
    var Ball = function(x , y , ismine){
                this.x = x;
                this.y = y;
                this.ismine = ismine;
                this.oldx = x;
                this.oldy = y;
                this.vx = 0;
                this.vy = 0;
                this.radius = ballRadius;
                this.inhole = false;this.moving = true;
            }
            Ball.prototype = {
                constructor:Ball,
                _paint:function(){
                    var b = this.ismine?document.getElementById("wb") : document.getElementById("yb")
                    if(b.complete) {
                        ctx.drawImage(b , this.x-this.radius , this.y-this.radius , 2*this.radius , 2*this.radius);
                    }
                    else {
                        b.onload = function(){
                            ctx.drawImage(b , this.x-this.radius , this.y-this.radius , 2*this.radius , 2*this.radius);
                        }
                    }
                },
                _run:function(t){
                    this.oldx = this.x;
                    this.oldy = this.y;
    
                    this.vx = Math.abs(this.vx)<0.1? 0 : (this.vx>0? this.vx-mcl*t : this.vx+mcl*t);
                     this.vy = Math.abs(this.vy)<0.1? 0 : (this.vy>0? this.vy-mcl*t : this.vy+mcl*t);
                    // this.vx += this.vx>0? -mcl*t : mcl*t;
                    // this.vy += this.vy>0? -mcl*t : mcl*t;
    
                     this.x += t * this.vx * pxpm;
                     this.y += t * this.vy * pxpm;
    
                     if((this.x<50 && this.y<50) || (this.x>370 && this.x<430 && this.y<50) || (this.x > 758 && this.y<50) || (this.x<46 && this.y>490) || (this.x>377 && this.x<420 && this.y>490) || (this.x > 758 && this.y>490)){
                         this.inhole = true;
                         if(this.ismine){
                             var that = this;
                             setTimeout(function(){
                                 that.x = 202;
                                 that.y = canvas.height/2;
                                 that.vx = 0;
                                 that.vy = 0;
                                 that.inhole = false;
                             } , 500)
                         }
                         else {
                             document.getElementById("shotNum").innerHTML = parseInt(document.getElementById("shotNum").innerHTML)+1
                         }
                     }
                     else {
                         if(this.y > canvas.height - (ballRadius+tbw) || this.y < (ballRadius+tbw)){
                             this.y = this.y < (ballRadius+tbw) ? (ballRadius+tbw) : (canvas.height - (ballRadius+tbw));
                             this.derectionY = !this.derectionY;
                             this.vy = -this.vy*0.6;
                         }
                         if(this.x > canvas.width - (ballRadius+tbw) || this.x < (ballRadius+tbw)){
                             this.x = this.x < (ballRadius+tbw) ? (ballRadius+tbw) : (canvas.width - (ballRadius+tbw));
                             this.derectionX = !this.derectionX;
                             this.vx = -this.vx*0.6;
                         }
                     }
                     this._paint();
    
                     if(Math.abs(this.vx)<0.1 && Math.abs(this.vy)<0.1){
                         this.moving = false;
                     }
                     else {
                         this.moving = true;
                     }
                }
            }
    复制代码

      球类的属性:x,y球的位置,vx,vy球的水平速度以及求得垂直速度,ismine代表是白球还是其他球(不同球在_paint方法中绘制的图片不一样),oldx,oldy用于保存球的上一帧位置,不过暂时还没用上,应该有用吧。_paint方法没什么好说的,_run方法就是跟踪小球位置,根据小球每一帧的时间来算出小球的位移增量以及速度增量,mcl和pxpm都是常量,mcl是摩擦力,pxpm是大概算个像素和现实转换比例。。。。然后就是碰撞检测,这个很容易理解了,就计算小球的位置有没有超过边界,超过了就反弹。不过这种碰撞检测很不严谨,如果真要做游戏建议用更复杂一些的。还有就是根据小球的速度来让小球静止。

      【辅助线】

    复制代码
    var dotLine = function(x0,y0,x1,y1){
                this.x0 = this.x0;
                this.y0 = this.y0;
                this.x1 = this.x1;
                this.y1 = this.y1;
                this.dotlength = 3;
                this.display = false;
            }
            dotLine.prototype = {
                constructor:dotLine,
                _ready:function(){
                    this.length = Math.sqrt(Math.pow(this.y1 - this.y0 , 2)+Math.pow(this.x1 - this.x0 , 2));
                    this.dotNum = Math.ceil(this.length/this.dotlength);
                },
                _paint:function(){
                    this._ready();
                    xadd = this.dotlength*(this.x1 - this.x0)/this.length;
                    yadd = this.dotlength*(this.y1 - this.y0)/this.length;
                    ctx.save();
                    ctx.beginPath();
                    for(var i=1;i<=this.dotNum;i++){
                        if(i%2!==0){
                            ctx.moveTo(this.x0+(i-1)*xadd , this.y0+(i-1)*yadd);
                            ctx.lineTo(this.x0+i*xadd , this.y0+i*yadd);
                        }
                    }
                    ctx.strokeStyle = "#FFF";
                    ctx.stroke();
                    ctx.beginPath();
                    ctx.arc(this.x1 , this.y1 , ballRadius-2 , 0 , 2*Math.PI);
                    ctx.stroke();
                    ctx.restore();
                }
            }
    复制代码

    就是画虚线,这个比较简单了,获取鼠标的位置和白球位置,然后在两者之间隔一段距离画条线,然后就成虚线了。

      【多球碰撞检测】

    复制代码
    function collision(){
                for(var i=0;i<balls.length;i++){
                    for(var j=0;j<balls.length;j++){
                        var b1 = balls[i],b2 = balls[j];
                        if(b1 !== b2 && !b1.inhole && !b2.inhole){
                            var rc = Math.sqrt(Math.pow(b1.x - b2.x , 2) + Math.pow(b1.y - b2.y , 2));
                            if(Math.ceil(rc) < (b1.radius + b2.radius)){
                                if(!b1.moving && !b2.moving) return;
                                //获取碰撞后的速度增量
                                var ax = ((b1.vx - b2.vx)*Math.pow((b1.x - b2.x) , 2) + (b1.vy - b2.vy)*(b1.x - b2.x)*(b1.y - b2.y))/Math.pow(rc , 2)
                                var ay = ((b1.vy - b2.vy)*Math.pow((b1.y - b2.y) , 2) + (b1.vx - b2.vx)*(b1.x - b2.x)*(b1.y - b2.y))/Math.pow(rc , 2)
                    //将速度增量赋给碰撞小球
                                b1.vx = b1.vx-ax;
                                b1.vy = b1.vy-ay;
                                b2.vx = b2.vx+ax;
                                b2.vy = b2.vy+ay;
                    //修正小球碰撞距离
                                var clength = ((b1.radius+b2.radius)-rc)/2;
                                var cx = clength * (b1.x-b2.x)/rc;
                                var cy = clength * (b1.y-b2.y)/rc;
                                b1.x = b1.x+cx;
                                b1.y = b1.y+cy;
                                b2.x = b2.x-cx;
                                b2.y = b2.y-cy;
                            }
                        }
                    }
                }
            }
    复制代码

      对所有小球进行遍历,计算两个小球的球心距离,如果小于两小球的半径和,则说明发生了碰撞。如果两个小球都是静止的,就不进行碰撞检测,否则进行计算碰撞后的速度增量,碰撞速度增量的求法可以直接看 小球碰撞的算法设计 ,里面讲的挺详细的,综合起来就得出了上面那一串式子了。

      将速度增量赋给碰撞小球。因为两个球碰撞那一帧,两个球是有部分重叠的,所以得进行位置修正,不然小球会一直处于碰撞然后就黏在一起了,位置修正的原理也简单,算出两球的球心距离,通过勾股定理计算出两球的重叠区域的宽度,然后把宽度除于2后赋给小球新的位置,新的位置就是两个球的半径刚好等于球心距。

      【鼠标动作】

    复制代码
    canvas.addEventListener("mousedown" , function(){
                if(balls[0].moving) return;
    
                document.querySelector(".shotPower").style.display = "block";
                document.querySelector(".shotPower").style.top = balls[0].y-60 + "px";
                document.querySelector(".shotPower").style.left = balls[0].x-40 +"px";
                document.getElementById("pow").className = "animate";
                var x = event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft - document.querySelector(".view").offsetLeft;
                var y = event.clientY + document.body.scrollTop + document.documentElement.scrollTop - document.querySelector(".view").offsetTop;
                dotline.display = true;
                dotline.x0 = balls[0].x;
                dotline.y0 = balls[0].y;
                dotline.x1 = x;
                dotline.y1 = y;
    
                window.addEventListener("mouseup" , muHandle , false);
                window.addEventListener("mousemove" , mmHandle , false);
    
                function mmHandle(){
                    var x = event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft - document.querySelector(".view").offsetLeft;
                    var y = event.clientY + document.body.scrollTop + document.documentElement.scrollTop - document.querySelector(".view").offsetTop;
                    dotline.x1 = x;
                    dotline.y1 = y;
                }
                function muHandle(){
                    var x = event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft - document.querySelector(".view").offsetLeft;
                    var y = event.clientY + document.body.scrollTop + document.documentElement.scrollTop - document.querySelector(".view").offsetTop;
    
                    var angle = Math.atan((y - balls[0].y)/(x - balls[0].x));
                    var h = document.getElementById("pow").offsetHeight/document.getElementById("powbar").offsetHeight;
                    var v = 60*h;
                    document.getElementById("pow").style.height = h*100+"%"
    
                    balls[0].vx = x - balls[0].x>0 ? v*Math.abs(Math.cos(angle)) : -v*Math.abs(Math.cos(angle)); 
                    balls[0].vy = y - balls[0].y>0 ? v*Math.abs(Math.sin(angle)) : -v*Math.abs(Math.sin(angle));
    
                    document.getElementById("pow").className = "";
    
                    window.removeEventListener("mouseup" , muHandle , false);
                    window.removeEventListener("mousemove" , muHandle , false);
                    dotline.display = false;
                    document.querySelector(".shotPower").style.display = "none";
                }
            },false);
    复制代码

      鼠标动作也比较简单,有js基础的基本上都没问题,就是鼠标按下后计算鼠标位置,然后产生辅助虚线,鼠标移动后修改辅助虚线的终点位置。鼠标按下的时候旁边产生一个力量计,我就只用用animation做动画了,然后鼠标按键抬起时通过计算力量计的大小来确定白球的速度,然后再分解成水平速度以及垂直速度赋给白球。同时取消鼠标移动以及鼠标抬起的事件绑定,把辅助虚线以及力量计隐藏。

      【动画舞台】

    复制代码
            function animate(){
                ctx.clearRect(0,0,canvas.width,canvas.height)
                 var t1 = new Date();
                 var t = (t1 - t0)/1000;
    
                 collision();
                 balls.foreach(function(){
                     if(!this.inhole) this._run(t);
                 });
                 if(dotline.display){
                     dotline.x0 = balls[0].x;
                     dotline.y0 = balls[0].y;
                     dotline._paint();
                 }
    
                 t0 = t1;
                 if(!animateStop){
                     if("requestAnimationFrame" in window){
                         requestAnimationFrame(animate);
                     }
                     else if("webkitRequestAnimationFrame" in window){
                         webkitRequestAnimationFrame(animate);
                     }
                     else if("msRequestAnimationFrame" in window){
                         msRequestAnimationFrame(animate);
                     }
                     else if("mozRequestAnimationFrame" in window){
                         mozRequestAnimationFrame(animate);
                     }
                     else {
                         setTimeout(animate , 16);
                     }
                 }
            }
    复制代码

    这个就是游戏每一帧的逻辑处理现场,如果小球进洞了,就不再进行绘制,如果辅助虚线的display属性设成false,就不进行辅助虚线的绘制,还有就是计算每一帧的时间。

      【常量与初始化】

    复制代码
    var canvas = document.getElementById("cas");
            var ctx = canvas.getContext('2d');
            var mcl = 1 , collarg = 0.8 , ballRadius = 15 , t0 = 0 , balls=[] , tbw = 32 , animateStop = true , powAnimation = false;
            var dotline;
            pxpm = canvas.width/20;
    
            window.onload = function(){
                var myball = new Ball(202 , canvas.height/2 , true);
                balls.push(myball);
                for(var i=0;i<6;i++){
                    for(var j=0;j<i;j++){
                        var other = new Ball(520+i*(ballRadius-2)*2 , (canvas.height-i*2*ballRadius)/2+ballRadius+2*ballRadius*j , false);
                        balls.push(other);
                    }
                }
                t0 = new Date();
                dotline = new dotLine(0,0,0,0);
    
                animateStop = false;
                animate();
            }
    复制代码

    实例化所有小球,把小球全部按照规律摆好,然后获取当前时间,实例化辅助虚线,动画开始。

    源码地址:https://github.com/whxaxes/canvas-test/tree/gh-pages/src/Game-demo/snooker

    底部的那两个方法是写两条线相交的判定和交点的获取,想用来写严谨一些的碰撞检测。。。。

  • 相关阅读:
    POJ 1300 Open Door
    POJ 2230 Watchcow
    codevs 1028 花店橱窗布置
    codevs 1021 玛丽卡
    codevs 1519 过路费
    codevs 3287 货车运输
    codevs 3305 水果姐逛水果街二
    codevs 1036 商务旅行
    codevs 4605 LCA
    POJ 1330 Nearest Common Ancestors
  • 原文地址:https://www.cnblogs.com/520lin/p/6031479.html
Copyright © 2011-2022 走看看