前言
《Pac-Man》上一次已经写过了,但是用vue写的,整体下来能玩,但是自己感觉不算流畅,而且游戏计算方面也有点欠缺,感兴趣的可看我上一篇文章。这次我又卷土重来,在三四天内用空闲时间,用canvas重构了一个完整且自认为完美的Pac-Man,无论是流畅度还是音乐,自测都感觉比较…perfect…
又因为此次中的代码还是比较繁琐的,我就不一一贴代码了,我就把整个游戏的做的过程走一遍,以及其中要注意的部分都会讲解到位。
此次游戏做的过程中,其实真的写起来还是有很多不明白的,自己也是慢慢的查阅、调试、询问等方式去慢慢啃下来。
废话不多说,直接走流程。

一、游戏代码分析
游戏展示用canvas绘制
<canvas id="myCanvas"></canvas>
游戏的核心代码就是用class类来分解游戏中各个元素
- Direction.js —— 方向
- Game.js —— 游戏入口
- Ghost.js —— 幽灵
- Pacman.js —— 吃豆人
- TileMap.js —— 地图
在核心代码class中,为了好区分,又把方法分为公共方法(不加#)和私有方法(加#)
例如:
draw() {
... }
#move() {
... }
二、游戏所需文件

(从上往下)
- 音乐文件(背景、死亡、吃金币、吃幽灵、吃闪光金币、获胜,全部用的是超级玛丽的音乐)
- 图片文件(墙壁砖、吃豆人的整个动效图片、幽灵本体、幽灵闪烁图片、金币图片、闪光金币图片)
- 核心文件(上面说的5个js文件)
- 样式文件(index.css)
- 入口文件(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();
}
}
}

(7)判断游戏是否赢了
找二维数组地图数据中只要没有金币即可
function checkGameWin() {
if(!gameWin) {
gameWin = tileMap.isWin();
if(gameWin) {
bgAudio.pause();
gameWinAudio.play();
}
}
}

(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]

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





