小游戏之欢乐吃豆人canvas重制版

作者用canvas重构了Pac-Man游戏,包括地图、吃豆人、幽灵等元素的实现,详细介绍了游戏的核心代码结构和逻辑,如方向控制、碰撞检测、游戏胜利和失败条件。文章强调了canvas在游戏开发中的应用,并分享了整个开发过程中的学习体验。

前言

《Pac-Man》上一次已经写过了,但是用vue写的,整体下来能玩,但是自己感觉不算流畅,而且游戏计算方面也有点欠缺,感兴趣的可看我上一篇文章。这次我又卷土重来,在三四天内用空闲时间,用canvas重构了一个完整且自认为完美的Pac-Man,无论是流畅度还是音乐,自测都感觉比较…perfect…

又因为此次中的代码还是比较繁琐的,我就不一一贴代码了,我就把整个游戏的做的过程走一遍,以及其中要注意的部分都会讲解到位。

此次游戏做的过程中,其实真的写起来还是有很多不明白的,自己也是慢慢的查阅、调试、询问等方式去慢慢啃下来。

废话不多说,直接走流程。

1e2ab47e351a7e3a47236da6cbad674.png

一、游戏代码分析

游戏展示用canvas绘制

<canvas id="myCanvas"></canvas>

游戏的核心代码就是用class类来分解游戏中各个元素

  • Direction.js —— 方向
  • Game.js —— 游戏入口
  • Ghost.js —— 幽灵
  • Pacman.js —— 吃豆人
  • TileMap.js —— 地图

在核心代码class中,为了好区分,又把方法分为公共方法(不加#)和私有方法(加#)
例如:

draw() {
   
    ... }
#move() {
   
    ... }

二、游戏所需文件

image.png
(从上往下)

  1. 音乐文件(背景、死亡、吃金币、吃幽灵、吃闪光金币、获胜,全部用的是超级玛丽的音乐)
  2. 图片文件(墙壁砖、吃豆人的整个动效图片、幽灵本体、幽灵闪烁图片、金币图片、闪光金币图片)
  3. 核心文件(上面说的5个js文件)
  4. 样式文件(index.css)
  5. 入口文件(index.html)

三、游戏基本布局

1. 页面布局
<div id="app">
  <!-- 游戏名 -->
  <h1 class="title">Pac-Man</h1>
  <!-- 游戏区 -->
  <div class="game">
    <canvas id="myCanvas"></canvas>
  </div>
</div>
<script src="js/Game.js" type="module"></script>
2. 样式
* {
   
    padding: 0; margin: 0; }
#app {
   
   
  width: 100%;
  height: 100vh;
  padding-top: 100px;
  position: fixed;
  display: flex;
  flex-direction: column;
  align-items: center;
  font-family: comic sans MS;
  background: linear-gradient(0deg, rgb(17, 51,161) 0%, rgb(136,34,195) 100%);
}
.title {
   
   
  color: lightgray;
  margin-bottom: 30px;
  font-size: 50px;
  user-select: none;
}

.game {
   
   
  background-color: #000;
}

#myCanvas {
   
   
  display: block;
  box-shadow: 10px 10px 20px black;
}

四、聊聊游戏核心代码

1. 入口类 Game.js

这个类的作用,主要就是存放游戏的基本配置信息,比如获取canvas,游戏定时器,游戏胜利、失败等

(1)在游戏开始,我们需要获取canvas

const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');

(2)还要配置需要的基本参数

const tileSize = 30; // 砖块尺寸(每个格子的长宽)                   
const speed = 2;     // 速度    
const gameOverAudio = new Audio('../audio/die.wav');  // 游戏结束音效
const gameWinAudio = new Audio('../audio/win.wav');   // 游戏胜利音效
const bgAudio = new Audio('../audio/bg.mp3');         // 游戏背景音乐
let gameOver = false; // 游戏是否结束
let gameWin = false;  // 游戏是否胜利

(3)又因为我们的游戏是高灵活性的,可以随意搭建自己想玩的地图,所以我们的canvas也要根据地图来生成大小

// 设置地图默认大小
tileMap.setCanvasSize(canvas);   

(4)页面加载时,游戏需要一个定时器,来实时监听页面的变化

// 游戏定时器
setInterval(gameLoop, 1000 / 75);

// 游戏运行
function gameLoop() {
   
   
  ctx.clearRect(0, 0, canvas.width, canvas.height);   // 每次先清空画布
  
  tileMap.draw(ctx);                                  // 画地图
  pacman.draw(ctx, pause(), ghost);                   // 画吃豆人
  ghost.forEach(e => e.draw(ctx, pause(), pacman));   // 画幽灵
  
  checkGameOver();  // 检查是否结束
  checkGameWin();   // 检查是否获胜
  drawGameDraw();   // 画游戏结束的提示框
  
  // 只有吃豆人动了,背景音乐才播放,游戏结束则停止播放
  if(pacman.madeFirstMove && !gameOver && !gameWin) {
   
   
    bgAudio.play();
  }
}

上面的个别代码比如传参啊,比如方法的内容,后面我们都会一一说明,因为这几个类基本上都会互相调用,要是放一起就会显得很乱,只能单拎出来说,最后再放一起整合就好了。

(5)定义暂停函数

// 是否暂停
// 如果 吃豆人没动,或者 游戏输了或者赢了,游戏都会处于暂停状态
function pause() {
   
   
  return !pacman.madeFirstMove || gameOver || gameWin;
}

(6)判断游戏是否结束

其实就是根据2D碰撞检测,检测幽灵是否碰到吃豆人了

下面是MDN官网的链接,感兴趣可以瞅瞅。

[MDN-web-docs](2D 碰撞检测 - 游戏开发环境 | MDN (mozilla.org))

function checkGameOver() {
   
   
  if(!gameOver) {
   
   
    // powerCoinActive:吃豆人是否吃到闪光豆豆
    // collidePacMan: 幽灵里面有没有哪个幽灵与吃豆人发生碰撞
    gameOver = ghost.some(item => !pacman.powerCoinActive && item.collidePacMan(pacman));
    if(gameOver) {
   
   
      bgAudio.pause();
      gameOverAudio.play();
    }
  }
}

image.png

(7)判断游戏是否赢了

找二维数组地图数据中只要没有金币即可

function checkGameWin() {
   
   
  if(!gameWin) {
   
   
    gameWin = tileMap.isWin();
    if(gameWin) {
   
   
      bgAudio.pause();
      gameWinAudio.play();
    }
  }
}

image.png

(8)游戏结束提示框

function drawGameDraw() {
   
   
  if(gameOver || gameWin) {
   
   
    let txt = 'You Win!'
    if(gameOver) {
   
   
      txt = 'Game Over!'
    }
    ctx.fillStyle = 'black';
    ctx.fillRect(0, canvas.height / 2.5, canvas.width, 100);
    ctx.font = '80px comic sans';
    
    // 渐变  官网有示例  直接拿来用
    const gradient = ctx.createLinearGradient(0, 0, canvas.width, 0);
    gradient.addColorStop('0', 'magenta');
    gradient.addColorStop('0.5', 'blue');
    gradient.addColorStop('1.0', 'red');

    ctx.fillStyle = gradient;
    ctx.textAlign = 'center';
    ctx.fillText(txt, canvas.width / 2, canvas.height / 1.75);
  }
}
2. 地图类 TileMap.js

TileMap主要存放关于地图的一切,墙壁、豆豆、闪光豆豆、判断撞墙、是否吃到豆豆、还有上面说的是否胜利等,为了能实时获取吃豆人和幽灵的位置信息,他们也是在这个里面获取的

(1)定义基本参数

constructor(tileSize) {
   
   
    this.tileSize = tileSize;

    // 金币
    this.coinDot = new Image();
    this.coinDot.src = "../img/coin1.png";

    // 闪光金币
    this.pinkCoinDot = new Image();
    this.pinkCoinDot.src = "../img/coinPink.png";

    // 墙
    this.wall = new Image();
    this.wall.src = "../img/wall.png";

    // 闪光金币定时器,说白一点,就是频繁切换金币图片,有闪烁的效果
    this.powerCoinTimerDefault = 40;
    this.powerCoinTimer = this.powerCoinTimerDefault;
    this.powerCoinDot = this.coinDot;
  }

(2)定义地图数据

依旧是一个二维数组

// 0 = 金币   
// 1 = 墙   
// 4 = 吃豆人  
// 5 = 空地
// 6 = 幽灵
// 7 = 闪光金币
map = [
    [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]
评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

张_大_炮

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

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

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

打赏作者

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

抵扣说明:

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

余额充值