基于HTML(canvas)的推箱子游戏.zip
有积分的自己自行下载吧,没有积分的兄弟可以评论留邮箱,看到后会给你们发过去。
游戏截图
设计过程
1. 设计目标:推箱子游戏
2. 设计思路:
1.1 观察游戏玩法 链接:http://www.4399.com/flash/202073.htm
2.2 分析游戏:游戏的玩法是将箱子通过小人推送到目标点,所有箱子到达目标点即为胜利。
小人移动的必要条件:1. 玩家按下方向键。 2. 移动终点有效(空白或箱子可被推动)。
小人的移动分析:
1)小人的移动终点为空白。此时小人直接到达即可。
2)小人的移动终点为墙壁。此时小人的移动无效。
3)小人的移动终点当前被箱子所占。此时需要对箱子的移动进行判定。
箱子移动的必要条件:1.箱子只可在小人的推动下才可进行移。2.移动终点有效(无障碍物)。
箱子的移动分析:
1)箱子的移动终点为空白。可被推动。
2)箱子的移动终点为墙壁或被其他箱子所占。此时箱子不可推动。
3)箱子的移动终点为目标点。箱子的移动后需要改变箱子的显示以达到提示用户效果。
3. 设计实现:
游戏使用 css3 中的 canvas 画布设计实现(。。。介绍自行百度吧)
没有基础可以去 part1 文件夹里面看基础。用到的核心就行画布画图片。
图片资源来自于 4399 游戏,为方便设计,图片已经 ps 处理,图片大小为 80*80 像素。画布使用大小50*50,需要进行缩放。
js 需要设计的东西:
核心代码:
1. 判断箱子或小人是否可移动。
2. 绘制游戏界面。
用户事件: 按下移动键后的处理函数。
4. 具体实现参见 part2 里面代码注释写的非常清楚
part1
index.html
<!DOCTYPE html> <!-- 声明当前文档为 html5文档 -->
<html lang="zh-CN"> <!-- 语言为中文 -->
<head>
<meta charset="UTF-8"> <!-- 字符编码:utf-8 -->
<title>推箱子</title> <!-- 页面的标题 -->
<style>
/* Flex 布局语法教程 https://www.runoob.com/w3cnote/flex-grammar.html */
/* 去除浏览器默认的内外边距 */
* {
margin: 0;
padding: 0;
}
html, body {
width: 100%;
height: 100%;
}
/* 使用 flex 布局, 主轴为水平方向, 起点在左端, 在主轴上的居中对齐 */
.game-frame-wrap {
width: 100%;
height: 100%;
display: flex;
flex-direction: row;
justify-content: center;
background-color: #eee; /* 方便区分 */
}
/* 使用 flex 布局, 主轴为垂直方向, 起点在上沿. 起点在上端, 在主轴上的居中对齐 */
.game-frame {
display: flex;
flex-direction: column;
justify-content: center;
background-color: white; /* 方便区分 */
}
#game-canvas {
width: 300px;
height: 300px;
background-color: green;
}
/* 以上的样式代码, 使游戏窗口保持在浏览器的水平垂直中间位置 */
</style>
</head>
<body>
<div class="game-frame-wrap">
<img id="wall" src="images/wall-sheet0.png" width="80" height="80" alt="">
<div class="game-frame">
<canvas id="game-canvas" width="300" height="300"></canvas>
</div>
</div>
<script>
window.onload = function () {
// HTML5 Canvas 使用教程 https://www.runoob.com/html/html5-canvas.html
// 获取 canvas dom 对象
var canvas = document.getElementById("game-canvas");
console.log(canvas, typeof canvas, "canvas");
// 获取在 canvas 画布上绘图的对象
var ctx = canvas.getContext("2d");
console.log(ctx, typeof ctx, "ctx");
// 1. 使用页面显示的图片绘图
var wallImg = document.getElementById("wall");
// 在画布 x:10, y:10 的位置开始绘图. 左上角坐标原点
ctx.drawImage(wallImg, 10, 10);
// 2. 使用图片资源直接绘图, 这种方式需要将图片资源加载好之后才能开始画图
var img = new Image();
img.src = "./images/crate-sheet0.png";
img.onload = function () {
ctx.drawImage(img, 100, 100);
}
};
</script>
</body>
</html>
crate-sheet0.png wall-sheet0.png
part2
index.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>推箱子</title>
<link rel="stylesheet" href="./css/index.css">
<script src="js/gameMap.js"></script>
<script type="text/javascript" src="js/index.js"></script>
</head>
<body>
<div class="game-frame-wrap">
<div class="game-frame">
<canvas id="game-canvas" width="600" height="600"></canvas>
<div id="game-info"></div>
</div>
</div>
</body>
</html>
gameMap.js
var gameMap=[];
// 0: 背景图片
// 7: 地板图片
// 1: 墙壁图片
// 2: 目标点图片
// 3: 箱子图片
// 4: 小人图片
// 5: 箱子就位
gameMap[0]=[
[0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,1,1,1,0,0,0,0,0],
[0,0,0,0,1,2,1,0,0,0,0,0],
[0,0,0,0,1,7,1,1,1,1,0,0],
[0,0,1,1,1,3,7,3,2,1,0,0],
[0,0,1,2,7,3,4,1,1,1,0,0],
[0,0,1,1,1,1,3,1,0,0,0,0],
[0,0,0,0,0,1,2,1,0,0,0,0],
[0,0,0,0,0,1,1,1,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0]];
gameMap[1]=[
[0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,1,1,1,1,1,0,0,0,0,0],
[0,0,1,4,7,7,1,0,0,0,0,0],
[0,0,1,7,3,3,1,0,1,1,1,0],
[0,0,1,7,3,7,1,0,1,2,1,0],
[0,0,1,1,1,7,1,1,1,2,1,0],
[0,0,0,1,1,7,7,7,7,2,1,0],
[0,0,0,1,7,7,7,1,7,7,1,0],
[0,0,0,1,7,7,7,1,1,1,1,0],
[0,0,0,1,1,1,1,1,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0]];
gameMap[2]=[
[0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,1,1,1,1,1,1,1,0,0,0],
[0,0,1,7,7,7,7,7,1,1,1,0],
[0,1,1,3,1,1,1,7,7,7,1,0],
[0,1,7,4,7,3,7,7,3,7,1,0],
[0,1,7,2,2,1,7,3,7,1,1,0],
[0,1,1,2,2,1,7,7,7,1,0,0],
[0,0,1,1,1,1,1,1,1,1,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0]];
gameMap[3]=[
[0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,1,1,1,1,0,0,0,0,0],
[0,0,1,1,7,7,1,0,0,0,0,0],
[0,0,1,4,3,7,1,0,0,0,0,0],
[0,0,1,1,3,7,1,1,0,0,0,0],
[0,0,1,1,7,3,7,1,0,0,0,0],
[0,0,1,2,3,7,7,1,0,0,0,0],
[0,0,1,2,2,5,2,1,0,0,0,0],
[0,0,1,1,1,1,1,1,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0]];
gameMap[4]=[
[0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,1,1,1,1,1,0,0,0,0],
[0,0,0,1,4,7,1,1,1,0,0,0],
[0,0,0,1,7,3,7,7,1,0,0,0],
[0,0,1,1,1,7,1,7,1,1,0,0],
[0,0,1,2,1,7,1,7,7,1,0,0],
[0,0,1,2,3,7,7,1,7,1,0,0],
[0,0,1,2,7,7,7,3,7,1,0,0],
[0,0,1,1,1,1,1,1,1,1,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0]];
index.js 部分代码
window.onload = function () {
var BLOCK_WIDTH = 50, BLOCK_HEIGHT = 50, WIDTH_SIZE = 12, HEIGHT_SIZE = 12;
var BLOCK_ENUM = {
background: 0,
floor: 7,
wall: 1,
point: 2,
crate: 3,
player: 4,
crate_ok: 5
}; // 定义一些常量
var currentGameMap; // 当前的游戏地图数据 12*12,每格 50px
var CURRENT_GAME_MAP; // 当前的游戏地图数据的原始数据
var currentLevel; // 当前等游戏地图的等级
var moveCount; // 移动次数
var imageMapping; // 存储图片数据
var personLocation = {x: 0, y: 0, position: 'down'}; // 存储小人的位置,方向
var gameInfo = document.getElementById('game-info');
var canvas = document.getElementById('game-canvas');
var ctx = canvas.getContext('2d');
initGame(0);
// 玩家按键事件监测,单次按下移动一次。不需要按着连续移动。
window.onkeydown = onkeydown;
window.onkeyup = function () {
window.onkeydown = onkeydown;
};
function onkeydown(event) {
switch (event.key) {
case 'ArrowUp': // 上键头 w
case 'w':
tryMove('up');
break;
case 'ArrowDown': // 下箭头 s
case 's':
tryMove('down');
break;
case 'ArrowLeft': // 左键头 a
case 'a':
tryMove('left');
break;
case 'ArrowRight':
case 'd':
tryMove('right'); // 右箭头 d
break;
}
window.onkeydown = null; // 与 window.onkeyup 一块限制按下按键触发一次事件
}
// 处理用户按键事件(小人移动),可以移动则移动
function tryMove(position) {
personLocation.position = position;
var p1, p2;
...
// 如果小人能够移动的话,更新游戏数据,并重绘地图
if(dealMove(p1, p2)){
moveCount++;
showGameInfo();
}
initGameMap(); // 绘当前更新了数据的地图, 不移动也重绘一次改变小人的朝向
// 延时一下等待画布画好
setTimeout(function () {
// 如果移动完成了进入下一关
if (checkFinish()) {
alert('恭喜过关!!');
initGame(currentLevel + 1)
}
},100)
}
// 处理小人移动
function dealMove(p1, p2) {
...
}
//判断是否推成功
function checkFinish() {
...
}
function initGame(level) {
if (level >= gameMap.length) {
alert('恭喜已经全部过关!!');
return
}
currentLevel = level; // 初始化地图等级
moveCount = 0; // 初始化移动次数
imageMapping = {
background: './images/background.png',
floor: './images/floor.png',
wall: './images/wall.png',
point: './images/point.png',
crate: './images/crate.png',
player: './images/player.png',
crate_ok: './images/crate_ok.png'
}; // 创建图片数据源
currentGameMap = gameMap[currentLevel]; // 初始化游戏地图数据
CURRENT_GAME_MAP = copyArray(currentGameMap);
// 预加载图片资源数据,加载好出初始化游戏地图
loadImage(imageMapping, function () {
initGameMap();
showGameInfo();
});
}
// 图片资源必须加载完毕才能使用,所以这里使用回调函数来初始化地图
function loadImage(srcs, callback) {
var loadedCount = 0, // 已经预加载好的图片数
imgCount = 0; // 需要预加载的图片数
for (var count in imageMapping) {
imgCount++;
}
for (var key in imageMapping) {
if(imageMapping.hasOwnProperty(key)){ // 对 对象的 key 进行一下检查
var src = imageMapping[key];
imageMapping[key] = new Image();
imageMapping[key].src = src;
imageMapping[key].onload = function () {
//判断是否所有的图片都预加载完成
if (++loadedCount >= imgCount && callback instanceof Function) {
callback();
}
}
}
}
}
// 绘画地图的函数
function initGameMap() {
for (var i = 0; i < HEIGHT_SIZE; i++) {
for (var j = 0; j < WIDTH_SIZE; j++) {
drawBlock(j, i)
}
}
}
function drawBlock(x, y) {
// 因为小人和目标点的背景是透明且小人有方向,所以小人和目标点要单独处理
var image = undefined;
var blockType = currentGameMap[y][x];
switch (blockType) {
case BLOCK_ENUM.background:
image = imageMapping.background;
break;
case BLOCK_ENUM.floor: // 地板图片
image = imageMapping.floor;
break;
case BLOCK_ENUM.wall: // 墙壁图片
image = imageMapping.wall;
break;
//case BLOCK_ENUM.point: // 目标点图片, 需要背景图单独画
// image = imageMapping.point;
// break;
case BLOCK_ENUM.crate: // 箱子图片
image = imageMapping.crate;
break;
//case BLOCK_ENUM.player: // 小人图片, 因为小人有面朝方向,所以要单独画
// image = imageMapping.player;
// personLocation.x = x;
// personLocation.y = y;
// break;
case BLOCK_ENUM.crate_ok: // 箱子已经到位
image = imageMapping.crate_ok;
break;
}
if (image) {
// 将80*80的图片压缩成50*50画
ctx.drawImage(image, 0, 0, 80, 80, BLOCK_WIDTH * x, BLOCK_HEIGHT * y, BLOCK_WIDTH, BLOCK_HEIGHT);
} else {
ctx.drawImage(imageMapping.floor, 0, 0, 80, 80, BLOCK_WIDTH * x, BLOCK_HEIGHT * y, BLOCK_WIDTH, BLOCK_HEIGHT);
if (blockType === BLOCK_ENUM.point) {
ctx.drawImage(imageMapping.point, 0, 0, 80, 80, BLOCK_WIDTH * x, BLOCK_HEIGHT * y, BLOCK_WIDTH, BLOCK_HEIGHT);
} else if (blockType === BLOCK_ENUM.player) {
drawPerson(x, y)
}
}
}
function drawPerson(x, y) {
personLocation.x = x;
personLocation.y = y;
var sx = 0, sy = 0; // 小人图像在图片中的位置
switch (personLocation.position) {
case 'up':
sy = 0;
break;
case 'down':
sy = 80;
break;
case 'left':
sy = 160;
break;
case 'right':
sy = 240;
break;
}
ctx.drawImage(imageMapping.player, sx, sy, 80, 80, BLOCK_WIDTH * x, BLOCK_HEIGHT * y, BLOCK_WIDTH, BLOCK_HEIGHT);
}
function showGameInfo(){
gameInfo.innerText = '第' + (currentLevel + 1) + '关,总共' + gameMap.length + '关。移动次数: ' + moveCount;
}
//克隆二维数组
function copyArray(arr) {
var b = [];
for (var i = 0; i < arr.length; i++) {
var c= [];
for (var j = 0; j < arr.length; j++) {
c.push(arr[i][j]);
}
b.push(c)
}
return b;
}
};