前段-用面向对象的方式开发一个水管小鸟的游戏

首先准备好各类空文件 index.js css html 和图片
在这里插入图片描述
图片是下面这些,如果没有的可在这里下载
在这里插入图片描述

2 开发开始

好了,基础准备工作完毕,开发开始,
首先,先把天空,大地,小鸟的盒子准备好,并为其添加样式
在这里插入图片描述
需要注意的是,天空和大地,都是宽度200%,这是因为我们使用绝对定位,让图片往左移来模拟移动,所以不能设置100%

效果如下
在这里插入图片描述

3.给小鸟设置飞行状态

给小鸟添加飞行状态的css,并给小鸟设置初始飞行状态和初始位置

在这里插入图片描述

4. 添加水管

之后就是添加水管了,注意下面的水管应该加上地面的高度也就是112px,这样才能正常的显示,不然就穿出地表了

在这里插入图片描述

以上,使用css 和html静态页面布局就完成了,接下来是使用js来控制行为了

创建js对象

因为标题说的是使用面向对象的方式来写这个游戏,
为了不做标题单,所以我先开始创建对象
,根据上面写的那些可以很轻易地得出

这个游戏拥有的对象为

天空
大地
水管

然后再仔细观察,该如何来判断小鸟是不是装上了水管呢?,小游戏,自然是把他们看做是一个个的矩形(这也是为什么我一直留着边框)
只需要js判断矩形重合了,那就是撞到了
在这里插入图片描述

所以在这里可以抽象出他们的共同父类,矩形,
所以这里新建一个class.js用来做对象

在这里插入图片描述

然后就需要想一想,这个类需要有什么成员

  1. 小鸟,水管,大地,天空,都是要移动的,所以这个类要有移动这个成员
  2. 宽度,高度,这是基本的
  3. 小鸟水管在地图的坐标
  4. 既然他们都能移动,那就有速度,速度,这是2d的那就得分为,横向速度,纵向速度

于是就有了下面这个基类

class Rectangle {
  constructor(width, height, left, top, xSpeed, ySpeed, dom) {
    this.width = width;
    this.height = height;
    this.left = left;
    this.top = top;
    this.xSpeed = xSpeed;
    this.ySpeed = ySpeed;
    this.dom = dom;
  }
  render() {
    this.dom.style.width = this.width + "px";
    this.dom.style.height = this.height + "px";
    this.dom.style.left = this.left + "px";
    this.dom.style.top = this.top + "px";
  }
  move(duration) {
    const xDis = this.xSpeed * duration;
    const yDis = this.ySpeed * duration;
    const newList = this.left + xDis
    const newTop = this.top + yDis;
    this.render()
  }
}

父类到这就定义完了,然后就是子类继承这个父类,
因为这些共有的类该有的属性大多都有了,比如移动啊,渲染啊,计算坐标之类的.所以子类我们可以写的非常简单,如下

天空&大地类定义

// 天空对象,继承自矩形
class Sky extends Rectangle {
  constructor() {
    const dom = document.querySelector(".sky");
    const styles = getComputedStyle(dom);
    super(parseFloat(styles.width), parseFloat(styles.height), 0, 0, -50, 0, dom);
  }

  // 天空每次运动到自身的一半,为避免超出(露馅)重新设置其left值
  onMoving() {
    if (this.left <= - this.width / 2) {
      this.left = 0;
    }
  }
}

// 大地的对象,和天空类似
class Land extends Rectangle {
  constructor(speed) {
    const styles = getComputedStyle(landDom);
    super(parseFloat(styles.width), parseFloat(styles.height), 0, parseFloat(styles.top), speed, 0, landDom);
  }

  onMoving() {
    if (this.left <= - this.width / 2) {
      this.left = 0;
    }
  }
}

小鸟对象

class Bird extends Rectangle {
  constructor() {
    const dom = document.querySelector(".bird");
    const styles = getComputedStyle(dom);
    super(parseFloat(styles.width), parseFloat(styles.height), parseFloat(styles.left), parseFloat(styles.top), 0, 0, dom);
    this.maxY = gameHeight - this.height - landHeight;
    this.timer = null;
    this.curSwingStatus = 1;
    this.g = 1500; // 每毫秒下降15像素
    this.render();
  }

  // 飞翔的方法,用于设置鸟飞翔的动作
  startFly() {
    if (this.timer) return;
    this.timer = setInterval(() => {
      this.curSwingStatus = (this.curSwingStatus + 1) == 4 ? 1 : (this.curSwingStatus + 1);
      this.render();
    }, 100);
  }

  // 停止飞翔
  stopFly() {
    clearInterval(this.timer);
    this.timer = null;
  }

  // 每一次渲染都要设置一下鸟的翅膀状态
  render() {
    this.dom.className = `bird swing${this.curSwingStatus}`;
    super.render();
  }

  // 鸟不能超出游戏区域
  onMoving() {
    if (this.top < 0) {
      this.top = 0;
    }
    else if (this.top > this.maxY) {
      this.top = this.maxY;
    }
  }

  // 鸟跳跃的方法
  jump() {
    this.ySpeed = -450;
  }

  // 鸟向下移动的方法
  move(duration) {
    super.move(duration);
    this.ySpeed += this.g * duration;
  }
}

水管类

// 管子对象
class Pipe extends Rectangle {
  constructor(height, top, speed, dom) {
    super(52, height, gameWidth, top, speed, 0, dom);
  }

  onMoving() {
    if (this.left < -this.width) {
      this.dom.remove();
    }
  }
}

function getRandomHeight(min, max) {
  return Math.floor(Math.random() * (max - min) + min);
}

// 管子对的对象
class PipePair {
  constructor(speed) {
    this.spaceHeight = 150;
    this.minHeight = 80;
    this.maxHeight = landTop - this.minHeight - this.spaceHeight;

    const upPipeHeight = getRandomHeight(this.minHeight, this.maxHeight);
    const upPipeDom = document.createElement("div");
    upPipeDom.className = "pipe up";
    this.upPipe = new Pipe(upPipeHeight, 0, speed, upPipeDom);

    const downPipeHeight = landTop - upPipeHeight - this.spaceHeight;
    const downPipeDom = document.createElement("div");
    downPipeDom.className = "pipe down";
    const downPipeDomTop = landTop - downPipeHeight;
    this.downPipe = new Pipe(downPipeHeight, downPipeDomTop, speed, downPipeDom);

    gameDom.appendChild(upPipeDom);
    gameDom.appendChild(downPipeDom);
  }

  // 管子对是否不可见(用于管子对工厂定期移除不可见的管子对)
  get hidden() {
    return this.upPipe.left <= -this.upPipe.width;
  }

  // 管子对移动的时候,需要上下两根管子一起东
  move(duration) {
    this.upPipe.move(duration);
    this.downPipe.move(duration);
  }
}

// 管子工厂
class PipePairFactory {
  constructor(speed) {
    this.speed = speed;
    this.pipePairs = [];
    this.newTimer = null;
    this.moveTimer = null;
    this.tick = 1500;
  }

  // 开始生产管子的方法
  startNew() {
    if (this.newTimer) return;

    this.newTimer = setInterval(() => {
      this.pipePairs.push(new PipePair(this.speed));
      for (let o = 0; o < this.pipePairs.length; o++) {
        const pipePair = this.pipePairs[o];
        if (pipePair.hidden) {
          this.pipePairs.splice(o, 1);
          o--;
        }
      }
    }, this.tick);
    this.startRun();
  }

  // 管子对开始运动的方法
  startRun() {
    if (this.moveTimer) return;
    this.moveTimer = setInterval(() => {
      this.pipePairs.forEach(pipePair => {
        pipePair.move(16 / 1000);
      });
    }, 16);
  }


  // 停止生产管子对的方法
  stopNew() {
    clearInterval(this.newTimer);
    clearInterval(this.moveTimer);
    this.newTimer = null;
    this.moveTimer = null;
  }
}
class Game {
  constructor() {
    const speed = -100;
    this.sky = new Sky();
    this.land = new Land(speed);
    this.bird = new Bird();
    this.pipePireFactory = new PipePairFactory(speed);
    this.tick = 16;
    this.speed = this.tick / 1000;
    this.timer = null;
    this.gameOver = false;
    this.bindEvent();
  }

  isGameOver() {
    if (this.bird.top == this.bird.maxY) {
      return true;
    }

    // 获取在小鸟区域的柱子对
    const pipePair = this.pipePireFactory.pipePairs.find(pipePair => {
      const pipePairLeft = pipePair.upPipe.left;
      const pipePairRight = pipePair.upPipe.left + pipePair.upPipe.width;
      return pipePairLeft <= this.bird.left && this.bird.left <= pipePairRight;
    });

    return pipePair && (this.isConflict(this.bird, pipePair.upPipe) || this.isConflict(this.bird, pipePair.downPipe));
  }

  isConflict(rec1, rec2) {
    // 碰撞检测:两矩形中心点距离小于两矩形长度的一半
    const centerX1 = rec1.left + rec1.width / 2;
    const centerY1 = rec1.top + rec1.height / 2;
    const centerX2 = rec2.left + rec2.width / 2;
    const centerY2 = rec2.top + rec2.height / 2;
    const disX = Math.abs(centerX1 - centerX2);
    const disY = Math.abs(centerY1 - centerY2);
    return disX < (rec1.width + rec2.width) / 2 &&
      disY < (rec1.height + rec2.height) / 2;
  }

  start() {
    if (this.timer) return;
    if (this.gameOver) {
      location.reload();
      return;
    }
    this.bird.startFly();
    this.pipePireFactory.startNew();
    this.timer = setInterval(() => {
      this.sky.move(this.speed);
      this.land.move(this.speed);
      this.bird.move(this.speed);
      if (this.isGameOver()) {
        this.stop();
        this.gameOver = true;
      }
    }, this.tick);
  }

  stop() {
    clearInterval(this.timer);
    this.timer = null;
    this.bird.stopFly();
    this.pipePireFactory.stopNew();
  }

  bindEvent() {
    window.addEventListener("keyup", e => {
      if (e.key == "Enter") {
        if (this.timer) {
          this.stop();
        }
        else {
          this.start();
        }
      }
      else if (e.key == " ") {
        this.bird.jump();
      }
    });
  }
}

var game = new Game();

game.start();

可以运行了

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Qayrup

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值