亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

JavaScript使用canvas實(shí)現(xiàn)flappy bird全流程詳解

 更新時(shí)間:2023年03月03日 08:48:17   作者:東方睡衣  
這篇文章主要介紹了JavaScript使用canvas實(shí)現(xiàn)flappy bird流程,canvas是HTML5提供的一種新標(biāo)簽,它可以支持JavaScript在上面繪畫(huà),控制每一個(gè)像素,它經(jīng)常被用來(lái)制作小游戲,接下來(lái)我將用它來(lái)模仿制作一款叫flappy bird的小游戲

簡(jiǎn)介

canvas 是HTML5 提供的一種新標(biāo)簽,它可以支持 JavaScript 在上面繪畫(huà),控制每一個(gè)像素,它經(jīng)常被用來(lái)制作小游戲,接下來(lái)我將用它來(lái)模仿制作一款叫flappy bird的小游戲。flappy bird(中文名:笨鳥(niǎo)先飛)是一款由來(lái)自越南的獨(dú)立游戲開(kāi)發(fā)者Dong Nguyen所開(kāi)發(fā)的作品,于2013年5月24日上線,并在2014年2月突然暴紅。

游戲規(guī)則

玩家只需要用一根手指來(lái)操控,點(diǎn)擊或長(zhǎng)按屏幕,小鳥(niǎo)就會(huì)往上飛,不斷的點(diǎn)擊就會(huì)不斷的往高處飛。放松手指,則會(huì)快速下降。所以玩家要控制小鳥(niǎo)一直向前飛行,然后注意躲避途中高低不平的管子。小鳥(niǎo)安全飛過(guò)的距離既是得分。當(dāng)然撞上就直接掛掉,只有一條命。

游戲素材

鏈接: https://pan.baidu.com/s/1ro1273TeIhhJgCIFj4vn_g?pwd=7vqh

提取碼: 7vqh 

開(kāi)始制作

初始化canvas畫(huà)布

這里主要是創(chuàng)建畫(huà)布,并調(diào)整畫(huà)布大小,畫(huà)布自適應(yīng)屏幕大小。

<!DOCTYPE html>
<html lang="zh">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style> body {
      margin: 0;
      padding: 0;
      overflow: hidden;
  } </style>
</head>
<body>
  <canvas id="canvas">
  當(dāng)前瀏覽器不支持canvas,請(qǐng)更換瀏覽器查看。
  </canvas>
  <script> /** @type {HTMLCanvasElement} */
    const canvas = document.querySelector('#canvas')
    const ctx = canvas.getContext('2d')
    canvas.width = window.innerWidth
    canvas.height = window.innerHeight
    window.addEventListener('resize', () => {
        canvas.width = window.innerWidth
        canvas.height = window.innerHeight
  }) </script>
</body>
</html> 

加載資源

圖片等資源的加載是異步的,只有當(dāng)所有的資源都加載完了才能開(kāi)始游戲,所以這里需要對(duì)圖片等資源進(jìn)行統(tǒng)一的監(jiān)控和管理。 將圖片資源用json進(jìn)行描述,通過(guò)fetch進(jìn)行統(tǒng)一加載。

// 資源管理器
class SourceManager {
  static images = {};
  static instance = new SourceManager();
  constructor() {
    return SourceManager.instance;}
  loadImages() {
    return new Promise((resolve) => {
      fetch("./assets/images/image.json")
      .then((res) => res.json())
      .then((res) => {
          res.forEach((item, index) => {
            const image = new Image();
            image.src = item.url;
            image.onload = () => {
              SourceManager.images[item.name] = image;
              ctx.clearRect(0, 0, canvas.width, canvas.height);
              ctx.font = "24px 黑體";
              ctx.textAlign = "center";
              ctx.fillText(`資源加載中${index + 1}/${res.length}...`, canvas.width / 2, (canvas.height / 2) * 0.618);
              if (index === res.length - 1) {
                console.log(index, "加載完成");
                resolve();
            }
          };
        });
      });
  });}
}
async function main() {
  // 加載資源
  await new SourceManager().loadImages();
}
main(); 

背景

為了適應(yīng)不同尺寸的屏幕尺寸和管子能正確渲染到對(duì)應(yīng)的位置,不能將背景圖片拉伸,要定一個(gè)基準(zhǔn)線固定背景圖片所在屏幕中的位置。我們發(fā)現(xiàn)背景圖并不能充滿整個(gè)畫(huà)面,上右下面是空缺的,這個(gè)時(shí)候需要使用小手段填充上,這里就用矩形對(duì)上部進(jìn)行填充。接下來(lái),需要讓背景有一種無(wú)限向左移動(dòng)的效果,就要并排繪制3張背景圖片,這樣在渲染的時(shí)候,當(dāng)背景向左移動(dòng)的距離dx等于一張背景圖的寬度時(shí),將dx=0,這樣就實(shí)現(xiàn)了無(wú)限向左移動(dòng)的效果,類似于輪播圖。

// 背景
class GameBackground {
  constructor() {
    this.dx = 0
    this.image = SourceManager.images.bg_day
    this.dy = 0.8 * (canvas.height - this.image.height)
    this.render()}
  update() {
    this.dx -= 1 
    if (this.dx + this.image.width <= 0) {
      this.dx = 0
  }
    this.render()}
  render() {
    ctx.fillStyle = '#4DC0CA'
    ctx.fillRect(0, 0, canvas.width, 0.8 * (canvas.height - this.image.height) + 10)
    ctx.drawImage(this.image, this.dx, this.dy)
    ctx.drawImage(this.image, this.dx + this.image.width, this.dy)
    ctx.drawImage(this.image, this.dx + this.image.width * 2, this.dy)}
}
let gameBg = null
main();
// 渲染函數(shù)
function render() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  gameBg.update();
  requestAnimationFrame(render)
}
?
async function main() {
  // 加載資源
  await new SourceManager().loadImages();
  // 背景
  gameBg = new GameBackground()
  // 渲染動(dòng)畫(huà)
  render()
} 

地面

地面要在背景的基礎(chǔ)上將地面圖上邊對(duì)齊基準(zhǔn)線(canvas.height * 0.8),并把下面空缺的部分通過(guò)和填補(bǔ)背景上半部分一致的方式填上。同時(shí)使用與背景無(wú)限向左移動(dòng)一樣的方法實(shí)現(xiàn)地面的無(wú)限向左移動(dòng)。

// 地面
class Land {
  constructor() {
    this.dx = 0;
    this.dy = canvas.height * 0.8;
    this.image = SourceManager.images.land;
    this.render();}
  update() {
    this.dx -= 1.5;
    if (this.dx + this.image.width <= 0) {
      this.dx = 0;
  }
    this.render();}
  render() {
    ctx.fillStyle = "#DED895";
    ctx.fillRect(
      0,
      canvas.height * 0.8 + this.image.height - 10,
      canvas.width,
      canvas.height * 0.2 - this.image.height + 10
  );
    ctx.drawImage(this.image, this.dx, this.dy);
    ctx.drawImage(this.image, this.dx + this.image.width, this.dy);
    ctx.drawImage(this.image, this.dx + this.image.width * 2, this.dy);}
}
let land = null
main();
// 渲染函數(shù)
function render() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  gameBg.update();
  requestAnimationFrame(render)
}
async function main() {
  // 加載資源
  await new SourceManager().loadImages();
  // 此處省略其他元素
  // 地面
  land = new Land()
  // 渲染動(dòng)畫(huà)
  render()
} 

管道

管道有上下兩部分,上部分管道需要貼著屏幕的頂部渲染,下部分要貼著地面也就是基準(zhǔn)線渲染,上下兩部分的管道長(zhǎng)度要隨機(jī)生成,且兩部分之間的距離不能小于80(我自己限制的);管道渲染速度為2s一次,并且也需要無(wú)限向左移動(dòng),這個(gè)效果和背景同理。

// 管道
class Pipe {
  constructor() {
    this.dx = canvas.width;
    this.dy = 0;
    this.upPipeHeight = (Math.random() * canvas.height * 0.8) / 2 + 30;
    this.downPipeHeight = (Math.random() * canvas.height * 0.8) / 2 + 30;
    if (canvas.height * 0.8 - this.upPipeHeight - this.downPipeHeight <= 80) {
      console.log("http:///小于80了///");
      this.upPipeHeight = 200;
      this.downPipeHeight = 200;
  }
    this.downImage = SourceManager.images.pipe_down;
    this.upImage = SourceManager.images.pipe_up;}
  update() {
    this.dx -= 1.5;// 記錄管道四個(gè)點(diǎn)的坐標(biāo),在碰撞檢測(cè)的時(shí)候使用this.upCoord = {tl: {x: this.dx,y: canvas.height * 0.8 - this.upPipeHeight,},tr: {x: this.dx + this.upImage.width,y: canvas.height * 0.8 - this.upPipeHeight,},bl: {x: this.dx,y: canvas.height * 0.8,},br: {x: this.dx + this.upImage.width,y: canvas.height * 0.8,},};this.downCoord = {bl: {x: this.dx,y: this.downPipeHeight,},br: {x: this.dx + this.downImage.width,y: this.downPipeHeight,},};
    this.render();}
  render() {
    ctx.drawImage(
      this.downImage,
      0,
      this.downImage.height - this.downPipeHeight,
      this.downImage.width,
      this.downPipeHeight,
      this.dx,
      this.dy,
      this.downImage.width,
      this.downPipeHeight
  );
    ctx.drawImage(
      this.upImage,
      0,
      0,
      this.upImage.width,
      this.upPipeHeight,
      this.dx,
      canvas.height * 0.8 - this.upPipeHeight,
      this.upImage.width,
      this.upPipeHeight
  );}
}
let pipeList = []
main();
function render() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  // 此處省略其他元素渲染步驟
  pipeList.forEach((item) => item.update());
  requestAnimationFrame(render)
}
async function main() {
  // 此處省略其他元素渲染步驟
  // 管道
  setInterval(() => {
    pipeList.push(new Pipe());
    // 清理移動(dòng)過(guò)去的管道對(duì)象,一屏最多展示3組,所以這里取大于3
    if (pipeList.length > 3) {
      pipeList.shift();
  }}, 2000);
  // 渲染動(dòng)畫(huà)
  render()
} 

笨鳥(niǎo)

小鳥(niǎo)要有飛行的動(dòng)作,這個(gè)通過(guò)不斷重復(fù)渲染3張小鳥(niǎo)不同飛行姿勢(shì)的圖片來(lái)實(shí)現(xiàn);還要通過(guò)改變小鳥(niǎo)的在Y軸的值來(lái)制作上升下墜的效果,并且能夠通過(guò)點(diǎn)擊或長(zhǎng)按屏幕來(lái)控制小鳥(niǎo)的飛行高度。

// 小鳥(niǎo)
class Bird {
  constructor() {
    this.dx = 0;
    this.dy = 0;
    this.speed = 2;
    this.image0 = SourceManager.images.bird0_0;
    this.image1 = SourceManager.images.bird0_1;
    this.image2 = SourceManager.images.bird0_2;
    this.loopCount = 0;
    this.control();
    setInterval(() => {
      if (this.loopCount === 0) {
        this.loopCount = 1;
    } else if (this.loopCount === 1) {
        this.loopCount = 2;
    } else {
        this.loopCount = 0;
    }
  }, 200);}
  // 添加控制小鳥(niǎo)的事件
  control() {
    let timer = true;
    canvas.addEventListener("touchstart", (e) => {
      timer = setInterval(() => {
        this.dy -= this.speed;
    });
      e.preventDefault();
  });
    canvas.addEventListener("touchmove", () => {
      clearInterval(timer);
  });
    canvas.addEventListener("touchend", () => {
      clearInterval(timer);
  });}
  update() {
    this.dy += this.speed;
    // 記錄小鳥(niǎo)四個(gè)點(diǎn)的坐標(biāo),在碰撞檢測(cè)的時(shí)候使用
    this.birdCoord = {
      tl: {
        x: this.dx,
        y: this.dy,
    },
      tr: {
        x: this.dx + this.image0.width,
        y: this.dy,
    },
      bl: {
        x: this.dx,
        y: this.dy + this.image0.height,
    },
      br: {
        x: this.dx + this.image0.width,
        y: this.dy + this.image0.height,
    },
  };
    this.render();}
  render() {
    // 渲染小鳥(niǎo)飛行動(dòng)作
    if (this.loopCount === 0) {
      ctx.drawImage(this.image0, this.dx, this.dy);
  } else if (this.loopCount === 1) {
      ctx.drawImage(this.image1, this.dx, this.dy);
  } else {
      ctx.drawImage(this.image2, this.dx, this.dy);
  }}
}
let bird = null
main();
function render() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  // 省略其他元素渲染
  bird.update();
  requestAnimationFrame(render);
}
async function main() {
  // 省略其他元素渲染
  // 笨鳥(niǎo)
  bird = new Bird()
  // 渲染動(dòng)畫(huà)
  render()
} 

我們發(fā)現(xiàn)小鳥(niǎo)好像是只美國(guó)鳥(niǎo),有點(diǎn)太freedom了~,不符合我們的游戲規(guī)則,要想辦法控制一下。

碰撞檢測(cè)

碰撞檢測(cè)的原理就是不斷檢測(cè)小鳥(niǎo)圖四個(gè)頂點(diǎn)坐標(biāo)是否在任一管道所占的坐標(biāo)區(qū)域內(nèi)或小鳥(niǎo)圖下方的點(diǎn)縱坐標(biāo)小于地面縱坐標(biāo)(基準(zhǔn)線),在就結(jié)束游戲。上面管道和小鳥(niǎo)類中記錄的坐標(biāo)就是為了實(shí)現(xiàn)碰撞檢測(cè)的。

let gameBg = null
let land = null
let bird = null
let pipeList = []
main();
function render() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  gameBg.update();
  land.update();
  bird.update();
  pipeList.forEach((item) => item.update());
  requestAnimationFrame(render);
  // 碰撞檢測(cè)-地面
  if (bird.dy >= canvas.height * 0.8 - bird.image0.height + 10) {
    gg();}
  //碰撞檢測(cè)-管道
  pipeList.forEach((item) => {
    if (
      bird.birdCoord.bl.x >= item.upCoord.tl.x - 35 &&
      bird.birdCoord.bl.x <= item.upCoord.tr.x &&
      bird.birdCoord.bl.y >= item.upCoord.tl.y + 10
  ) {
      gg();
  } else if (
      bird.birdCoord.tl.x >= item.downCoord.bl.x - 35 &&
      bird.birdCoord.tl.x <= item.downCoord.br.x &&
      bird.birdCoord.tl.y <= item.downCoord.bl.y - 10
  ) {
      gg();
  }});
}
async function main() {
  // 加載資源
  await new SourceManager().loadImages();
  // 背景
  gameBg = new GameBackground()
  // 地面
  land = new Land()
  // 笨鳥(niǎo)
  bird = new Bird()
  // 管道
  setInterval(() => {
    pipeList.push(new Pipe());
    // 清理移動(dòng)過(guò)去的管道對(duì)象,一屏最多展示3組,所以這里取大于3
    if (pipeList.length > 3) {
      pipeList.shift();
  }}, 2000);
  // 渲染動(dòng)畫(huà)
  render()
}
function gg() {
  const ggImage = SourceManager.images.text_game_over;
  ctx.drawImage(
    ggImage,
    canvas.width / 2 - ggImage.width / 2,
  (canvas.height / 2) * 0.618);
}; 

效果

增加碰撞檢測(cè)后,小鳥(niǎo)碰到管道或地面就會(huì)提示失敗。 此篇展示了基本的核心邏輯,完整游戲地址和源碼在下方鏈接。

到此這篇關(guān)于JavaScript使用canvas實(shí)現(xiàn)flappy bird全流程詳解的文章就介紹到這了,更多相關(guān)JS flappy bird內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論