【默默努力】fishingGame

这个捕鱼游戏挺有意思的,通过发射子弹,打鱼。打鱼的子弹会消耗金币,但是打鱼如果打到了鱼,就会奖励金币的数量。
我如果写这个的话,应该会画一个 背景海底,然后生成很多鱼的图片,还要有一个大炮,金币。大炮会发射子弹,角度不同发摄子弹的方向不同。
发射子弹就消耗金币,如果打中鱼了就奖励金币,大炮两边的加号和减号就是控制让子弹连续发射的。不过此时发送炸弹的角度不好控制。
接下来我们看效果

先放下作者大大的项目地址:https://github.com/JayHowe/fishingGame
接下来我们分析代码
页面初始化绘制图片

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <style type="text/css">
    body {
        text-align: center;
        background-color: #000;
    }

    #c1 {
         800px;
        height: 600px;
        background: url(img/game_bg_2_hd.jpg) no-repeat;
        margin: 0 auto;
    }
    </style>
    <script type="text/javascript" src="js/common.js"></script>
    <script type="text/javascript" src="js/drawRect.js"></script>
    <script type="text/javascript" src="js/sprite.js"></script>
    <script type="text/javascript" src="js/fish.js"></script>
    <script type="text/javascript" src="js/cannon.js"></script>
    <script type="text/javascript" src="js/button.js"></script>
    <script type="text/javascript" src="js/bullet.js"></script>
    <script type="text/javascript" src="js/coin.js"></script>
    <script>
    window.onload = function() {
        // 绘制背景
        let oC = document.getElementById('c1');
        let gd = oC.getContext('2d');

        let lastFire=0;
        let fired=false;
        let MAX_FISH=30;
        const coinCollector={x:106,y:576};
        //游戏总得分
        let playerScore=1000;

        const W = oC.width,
            H = oC.height;
            // 加载图片
        loadImages(_resources, function() {
            //炮台
            let tower = new Sprite(new DrawRect(_imgs.bottom, 0, 0, 756, 71, ));
            tower.x = 400;
            tower.y = H - 71 / 2 + 1;

            //炮
            let cannon = new Cannon(1);

            cannon.x = 443;
            cannon.y = 574;

            //炮弹
            let bullets=[];

            //鱼
            let fishs=[];

            // 金币
            let coins=[];

            //分数数字
            let scores=[];

            for(let i=0;i<6;i++){
                let sprite=new Sprite(new DrawRect(_imgs.number,0,9*24,20,24));
                sprite.x=51+23*i;
                sprite.y=586;
                scores.push(sprite);
            };

            //鼠标事件
            oC.onmousemove = ev => {
                let a = ev.offsetX - cannon.x;
                let b = ev.offsetY - cannon.y;
                let ang = a2d(Math.atan2(b, a)) + 90;
                cannon.rotation = ang;
            }

            //加号、减号
            let btnMinus = new Button(
                new DrawRect(_imgs.bottom, 135, 75, 36, 28),
                new DrawRect(_imgs.bottom, 91, 75, 36, 28)
            );
            btnMinus.x = 371;
            btnMinus.y = 566;

            let btnPlus = new Button(
                new DrawRect(_imgs.bottom, 47, 75, 36, 28),
                new DrawRect(_imgs.bottom, 3, 75, 36, 28)
            );
            btnPlus.x = 516;
            btnPlus.y = 566;

            btnMinus.onclick = function() {
                if (cannon.type > 1) {
                    cannon.setType(cannon.type - 1);
                } else {
                    cannon.setType(1);
                }
            };
            btnPlus.onclick = function() {
                if (cannon.type < 7) {
                    cannon.setType(cannon.type + 1);
                } else {
                    cannon.setType(7);
                }
            };

            let aBtn = [btnMinus, btnPlus];

            oC.onmousedown = function(ev) {
                //检测按钮
                aBtn.forEach(btn => {
                    btn.down(ev.offsetX, ev.offsetY);
                });

                if(Date.now()-lastFire >= 300) {
                    lastFire=Date.now();
                    //炮弹
                    let bullet=new Bullet(cannon.type, cannon.x,cannon.y,cannon.rotation);
                    bullets.push(bullet);

                    playerScore-=cannon.type*2;

                    fired=true;
                }
                
            };
            oC.onmouseup = function(ev) {
                aBtn.forEach(btn => {
                    btn.up(ev.offsetX, ev.offsetY);
                });
            };

            function animate() {
                requestAnimationFrame(animate);

                //生成鱼
                if(rnd(1,20)==1 && fishs.length<MAX_FISH) {
                    let fish=new Fish(rnd(1,5));
                    if(rnd(0,2)==0) {
                        //左边
                        fish.x=-100;
                        fish.rotation=90;
                    }else {
                        //右边
                        fish.x=W+100;
                        fish.rotation=-90;
                    }
                    fish.y=rnd(0,H-100);
                    fishs.push(fish);
                }

                gd.clearRect(0, 0, oC.width, oC.height);

                coins=coins.filter(coin=>{
                    coin.move(coinCollector.x,coinCollector.y);
                    coin.nextFrame();
                    coin.draw(gd);

                    if(Math.abs(coin.x-coinCollector.x)<5 && Math.abs(coin.y-coinCollector.y)<5) {
                        playerScore+=50;
                        return false;
                    }else {
                        return true;
                    }
                });
                tower.draw(gd);

                bullets=bullets.filter(bullet=>{
                    bullet.move();
                    bullet.draw(gd);
                    return !bullet.outRect(-100,-100,W+200,H+200); 
                });
                // console.log(bullets.length);

                fishs=fishs.filter(fish=>{
                    fish.move();
                    fish.draw(gd); 
                    fish.nextFrame();
                    return !fish.outRect(-100,-100,W+200,H+200); 
                });
                // console.log(fishs.length);



                cannon.draw(gd);
                if(fired) {
                    ret=cannon.nextFrame();
                    if(ret) {
                        fired=false;
                    }
                }

                btnMinus.draw(gd);
                btnPlus.draw(gd);

                //碰撞
                fishs=fishs.filter(fish=>{
                    let colled=false;
                    bullets=bullets.filter(bullet=>{
                        if(!colled && fish.collTest(bullet)){ 
                            if(Math.random()<bullet.type*10/(10+(fish.type-1)*20)) {
                               colled=true;
                            }
                            return false; 
                        }else {
                            return true;
                        }
                    });
                    
                    if(colled) {
                        fish.isdead=true;
                        fish.speed=0;

                        setTimeout(function() {
                            //金币
                            let a=fish.x-coinCollector.x;
                            let b=coinCollector.y-fish.y;

                            let i=0;
                            let timer=setInterval(function(){
                                let coin=new Coin(1,fish.x,fish.y);
                                // coin.x+=rnd(-50,50);
                                // coin.y+=rnd(-50,50);
                                coins.push(coin);
                                i++;

                                if(i==Math.pow(2,fish.type)) {
                                    clearInterval(timer);
                                }
                            },60);
                            
                            fishs=fishs.filter(item=>item!=fish);
                        },500);

                        return true;
                    }else {
                        return true;
                    }
                });

                //分数
                let str=playerScore+'';
                while(str.length<6) {
                   str='0'+str; 
                }
                scores.forEach((score,index)=>{
                    playerScore
                    score.setDrawRect(new DrawRect(_imgs.number,0,(9-parseInt(str[index]))*24,20,24));
                    score.draw(gd);
                });
            }
            requestAnimationFrame(animate);

        });
    }
    </script>
</head>
<body>
    <canvas id="c1" width="800" height="600"></canvas>
</body>
</html>
//bullet
//绘制子弹形状
class Bullet extends Sprite{
	constructor(type,x=0,y=0,rotation=0){
		const SIZE=[
			null,
			new DrawRect(_imgs.bullet,86,0,24,26),
			new DrawRect(_imgs.bullet,61,0,25,29),
			new DrawRect(_imgs.bullet,32,36,29,30),
			new DrawRect(_imgs.bullet,30,82,29,33),
			new DrawRect(_imgs.bullet,0,82,30,34),
			new DrawRect(_imgs.bullet,30,0,31,26),
			new DrawRect(_imgs.bullet,0,44,32,38)
		];

		super(SIZE[type],x,y,rotation);

		this.type=type;
		this.speed=5;

		this.radius=14;
	}
}

绘制按钮

//button
class Button extends Sprite{
  constructor(drawRectNormal, drawRectActive, x=0, y=0, rotation=0){
    super(drawRectNormal, x, y, rotation);

    this.drawRectNormal=drawRectNormal;
    this.drawRectActive=drawRectActive;

    this.downAtMe=false;
  }

  down(x, y){
    if(this.inRect(x, y)){
      this.setDrawRect(this.drawRectActive);

      this.downAtMe=true;
    }else{
      this.downAtMe=false;
    }
  }
  up(x, y){
    this.setDrawRect(this.drawRectNormal);

    if(this.inRect(x, y) && this.downAtMe){
      //触发onclick
      this.onclick && this.onclick();
    }
  }
}

绘制大炮

//cannon.js
class Cannon extends Sprite {
    constructor(type, x = 0, y = 0, rotation = 0) {
        if (type > 7 || type < 1) {
            throw new Error('unkonw cannon type');
        }
        const SIZE = [
            null,
            { w: 74, h: 74 },
            { w: 74, h: 76 },
            { w: 74, h: 76 },
            { w: 74, h: 83 },
            { w: 74, h: 85 },
            { w: 74, h: 90 },
            { w: 74, h: 94 }
        ];

         //父级
        super(
          new DrawRect(_imgs[`cannon${type}`], 0, 0, SIZE[type].w, SIZE[type].h),
          x, y, rotation
        );

        this.SIZE=SIZE;

        this.setType(type);

        this.MAX_FRAME=5;
    }

    setType(type){
        this.type = type;
        this.setDrawRect(
          new DrawRect(_imgs[`cannon${type}`], 0, 0, this.SIZE[type].w, this.SIZE[type].h)
        );
    }
}

金币

//jscoin.js
class Coin extends Sprite{
	constructor(type,x=0,y=0,rotation=0){
		const SIZE=[
			null,
			new DrawRect(_imgs.coin1,0,0,60,60),
			new DrawRect(_imgs.coin2,0,0,60,60)
		];

		super(SIZE[type],x,y,rotation);

		this.MAX_FRAME=10;
		this.speed=10;
	}


}

初始化加载图片

//common.js
let _imgs=null;

const _resources={
  fish1: 'img/fish1.png',
  fish2: 'img/fish2.png',
  fish3: 'img/fish3.png',
  fish4: 'img/fish4.png',
  fish5: 'img/fish5.png',
  cannon1: 'img/cannon1.png',
  cannon2: 'img/cannon2.png',
  cannon3: 'img/cannon3.png',
  cannon4: 'img/cannon4.png',
  cannon5: 'img/cannon5.png',
  cannon6: 'img/cannon6.png',
  cannon7: 'img/cannon7.png',
  bottom: 'img/bottom.png',
  bullet: 'img/bullet.png',
  coin1: 'img/coinAni1.png',
  coin2: 'img/coinAni2.png',
  number: 'img/number_black.png',
};

function loadImages(json, fn){
  let res={};
  let complete=0;
  let total=0;

  for(let name in json){
    total++;

    let oImg=new Image();

    res[name]=oImg;

    oImg.onload=function (){
      complete++;

      if(complete==total){
        _imgs=res;
        fn();
      }
    };

    oImg.onerror=function (){
      alert('图片加载失败'+oImg.src);
    };

    oImg.src=json[name];
  }
}

function d2a(n){
  return n*Math.PI/180;
}
function a2d(n){
  return n*180/Math.PI;
}
function rnd(n, m){
  return Math.floor(Math.random()*(m-n)+n);
}

画方形的类

//jsdrawRect.js
class DrawRect{
	constructor(img,sx,sy,sw,sh){
		if(!img || !sw || !sh) {
			throw new Error('img and sw and sh is required');
		}
		this.img=img;
		this.sx=sx;
		this.sy=sy;
		this.sw=sw;
		this.sh=sh;
	}
}

定义的鱼的类型

//jsfish.js
class Fish extends Sprite {
    constructor(type, x = 0, y = 0, rotation = 0) {
        if (type > 5 || type < 1) {
            throw new Error('unkonw fish type');
        }
        const SIZE = [
            null,
            { w: 55, h: 37, r: 12 },
            { w: 78, h: 64, r: 18 },
            { w: 72, h: 56, r: 15 },
            { w: 77, h: 59, r: 15 },
            { w: 107, h: 122, r: 23 }
        ];
        super(new DrawRect(_imgs[`fish${type}`], 0, 0, SIZE[type].w, SIZE[type].h), x, y, rotation);

        this.type = type;
        this.curFrame = 0;
        this.MAX_FRAME = 4;

        this.speed = rnd(1, 4);

        this.frameRate = 5;

        this.radius=SIZE[type].r;

        //死鱼
        this.isdead=false;
    }

    draw(gd) {
        if(this.isdead) {
            this.curFrame+=4;
        }

        if (this.rotation == -90) {
            this.scaleY = -1;
        }

        this.rotation -= 90;
        super.draw(gd);
        this.rotation += 90;


        if (this.rotation == -90) {
            this.scaleY = 1;
        }


        if(this.isdead) {
            this.curFrame-=4;
        }
    }
}

绘制鱼的类型

//jssprite.js

class Sprite {
    //w,h,x,y,rotate
    //draw(),碰撞检测()
    constructor(drawRect, x = 0, y = 0, rotation = 0) {
        if (!(drawRect instanceof DrawRect)) {
            throw new Error('img must be DrawRect');
        }
        this.setDrawRect(drawRect);
        this.x = x;
        this.y = y;
        this.rotation = rotation;

        this.speed = 0;

        //动画
        this.MAX_FRAME=0;
        this.curFrame=0;

        this.scaleX=1;
        this.scaleY=1;

        this.frameRate=1;
        this.frameRateNow=0;

        // 碰撞检测
        this.radius=0;
    }

    setDrawRect(drawRect) {
        this.drawRect = drawRect;
        this.width = drawRect.sw;
        this.height = drawRect.sh;
    }

    nextFrame(){
        this.frameRateNow++;
        // console.log(this.frameRateNow);

        if(this.frameRateNow==this.frameRate) {
            this.frameRateNow=0;

            this.curFrame++;
            if(this.curFrame>=this.MAX_FRAME) {
                this.curFrame=0;
                return true;
            }

            return false;
        }
    }

    draw(gd) {
        gd.save();

        gd.translate(this.x, this.y);
        gd.rotate(d2a(this.rotation));
        gd.scale(this.scaleX,this.scaleY);

        gd.drawImage(
            this.drawRect.img,
            this.drawRect.sx, this.drawRect.sy+this.height*this.curFrame, this.width, this.height,
            -this.width / 2, -this.height / 2, this.width, this.height,
        );
        gd.restore();
    }

    inRect(x, y) {
        if (
            this.x - this.width / 2 <= x && x <= this.x + this.width / 2 &&
            this.y - this.height / 2 <= y && y <= this.y + this.height / 2
        ) {
            return true;
        } else {
            return false;
        }
    }

    outRect(x,y,w,h){
        if(this.x<x || this.y<y || this.x>x+w || this.y>y+h) {
            return true;
        }else {
            return false;
        }
    }

    move(x,y) {
        if(arguments.length == 0) {
            let x_speed = this.speed * Math.sin(d2a(this.rotation));
            let y_speed = this.speed * Math.cos(d2a(this.rotation));
            this.x += x_speed;
            this.y -= y_speed;
        }else {
            this.x+=(x-this.x)/20;
            this.y+=(y-this.y)/20;
        }
        
    }

    collTest(other){
        return Math.sqrt(Math.pow(this.x-other.x,2)+Math.pow(this.y-other.y,2))<this.radius+other.radius;
    }


}

后记:代码我并没有完全看懂

原文地址:https://www.cnblogs.com/smart-girl/p/11448026.html