本文详细介绍如何使用JavaScript面向对象思想开发经典贪吃蛇游戏。通过封装Food、Snake和Game三个核心类,分别处理食物生成渲染、蛇身移动碰撞检测及全局游戏逻辑控制。文章涵盖HTML5地图构建、CSS样式布局及完整的JS代码实现,适合前端初学者学习OOP实战与DOM操作。
贪吃蛇游戏概述与设计思路
本文基于JavaScript面向对象编程思想,分享一个经典的贪吃蛇游戏实现案例,供读者参考学习。
在进行面向对象设计时,首先需要识别项目中的核心实体。在本案例中,主要涉及三个具体对象:食物(Food)、蛇(Snake)以及游戏主控逻辑(Game)。其中,游戏主控对象并非必须,只要能将核心业务逻辑清晰呈现即可。
接下来对各对象的属性与方法进行详细拆解: 1)Food对象:包含位置、大小、颜色等属性;具备在页面渲染及随机生成新位置的方法。 2)Snake对象:包含位置、大小、总节数(用于计分)及颜色属性;具备页面渲染及移动逻辑(移动时需进行碰撞检测等方法)。 3)Game对象:负责统筹整个游戏的运行逻辑。
确定设计后,开始编写代码。
HTML地图与CSS样式构建
第一步是构建基础的静态页面,即绘制游戏地图。
首先编写HTML结构。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="css/index.css" >
<script src="js/food.js"></script>
<script src="js/snake.js"></script>
<script src="js/game.js"></script>
<script src="js/main.js"></script>
<title>贪吃蛇</title>
</head>
<body>
<div class="map"></div>
</body>
</html>
其次编写CSS样式。若使用边框作为地图边界,必须设置box-sizing属性为border-box,以消除因边框宽度导致的坐标计算误差,确保食物与蛇头坐标对齐准确。
* {
margin: 0;
padding: 0;
}
.map {
position: relative;
height: 600px;
width: 800px;
border: 1px solid #333;
margin: 0 auto;
/* 盒子模型去除边框 */
box-sizing: border-box;
}
Food食物类实现
第二步是编写Food对象,代码中包含详细注释说明。
//cwen加载页面所有元素
window.addEventListener('load', function() {
//cwen自调用函数,开启一个新的作用域,避免命名冲突
(function() {
//cwen定义全局变量
//实物数组
var elements = [];
//cwen实物
function Food(options) {
options = options || {};
this.x = options.x || 0;
this.y = options.y || 0;
this.width = options.width || 20;
this.height = options.height || 20;
this.color = options.color || 'yellow';
}
//cwen随机数函数
function getRandom(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
//cwen渲染
Food.prototype.render = function(map) {
//删除之前的食物
remove();
//todo动态创建div实物
var div = document.createElement('div');
map.appendChild(div);
//把div添加给数组
elements.push(div);
//todo随机设置x,y的值(实物的位置)-----在map中生成随机位置
// ! 值 = Math.floor(Math.random() * 可能值得总数 + 第一个可能的值)
this.x = getRandom(0, map.offsetWidth / this.width - 1) * this.width;
this.y = getRandom(0, map.offsetHeight / this.height - 1) * this.height;
div.style.position = 'absolute';
div.style.left = this.x + 'px';
div.style.top = this.y + 'px';
div.style.width = this.width + 'px';
div.style.height = this.height + 'px';
div.style.backgroundColor = this.color;
}
function remove() {
//为了删除干净,从索引最大的开始循环删除
for (var i = elements.length - 1; i >= 0; i--) {
//删除遍历到的div
elements[i].parentNode.removeChild(elements[i]);
//删除数组中的元素1)第几个开始,2)要删除个数
elements.splice(i, 1);
}
}
//把Food开放出去
window.Food = Food;
})()
//cwen测试
// var map = document.querySelector('.map');
// var options = { x: 20, y: 20, width: 30, height: 30, color: 'green' };
// //todo不传值默认为自定义food
// var food = new Food();
// food.render(map);
})
Snake蛇身类实现
第三步是编写Snake对象。
window.addEventListener('load', function() {
(function() {
//记录蛇的每一节
var elements = [];
//cwen立即执行函数,开启新的作用于,避免命名冲突
function Snake(options) {
options = options || {};
//对象(蛇)每节的大小
this.width = options.width || 20;
this.height = options.height || 20;
//cwen蛇的总节数(计分)
this.mark = options.mark || 0;
//对象的移动方向
this.direction = options.direction || 'right';
//对象的身体(蛇节)
this.kont = [{ x: 3, y: 2, color: 'red' }, { x: 2, y: 2, color: 'black' }, { x: 1, y: 2, color: 'black' }];
}
//cwen渲染对象
Snake.prototype.render = function(map) {
//移除之前的蛇
remove();
//循环输出对象的身体(蛇节)
for (var i = 0, len = this.kont.length; i < len; i++) {
var obj = this.kont[i];
var div = document.createElement('div');
map.appendChild(div);
//将蛇节添加入数组
elements.push(div);
//添加样式
div.style.position = 'absolute';
div.style.width = this.width + 'px';
div.style.height = this.height + 'px';
div.style.left = obj.x * this.width + 'px';
div.style.top = obj.y * this.height + 'px';
div.style.backgroundColor = obj.color;
}
}
//cwen控制蛇移动的方法
//todo传参food,map 在game中调用move方法也要传入相应参数
Snake.prototype.move = function(food, map) {
//控制蛇节的移动(当前蛇节到下一个蛇节的位置)
for (var i = this.kont.length - 1; i > 0; i--) {
this.kont[i].x = this.kont[i - 1].x;
this.kont[i].y = this.kont[i - 1].y;
}
//判断并控制蛇头移动,判断蛇头移动方向
var head = this.kont[0];
switch (this.direction) {
case 'right':
head.x += 1;
break;
case 'left':
head.x -= 1;
break;
case 'top':
head.y -= 1;
break;
case 'bottom':
head.y += 1;
break;
}
//蛇头碰到食物时处理
// cwen判断蛇头是否和食物坐标重合
var headX = head.x * this.width;
var headY = head.y * this.height;
if (headX == food.x && headY == food.y) {
//1,增加蛇节(找到最后一根蛇节,然后添加给创建的蛇数组)
var last = this.kont[this.kont.length - 1];
this.kont.push({ x: last.x, y: last.y, color: last.color });
//cwen求出蛇节的总个数(计分)
var mark = this.mark++;
//2,重新渲染食物
food.render(map);
}
}
//删除之前的蛇
function remove() {
for (var i = elements.length - 1; i >= 0; i--) {
elements[i].parentNode.removeChild(elements[i]);
elements.splice(i, 1);
}
}
//把Snake构造函数暴露出去
window.Snake = Snake;
})()
//测试
// var map = document.querySelector('.map');
// var snake = new Snake();
// snake.render(map);
})
Game游戏主控逻辑
第四步是编写Game对象,其中包含一个无敌版本的实现,代码细节均有注释。
window.addEventListener('load', function() {
(function() {
//改变计时器内this指向
var that;
function Game(map) {
// var options = { x: 20, y: 20, width: 30, height: 30, color: 'green' };
this.food = new Food();
this.snake = new Snake();
this.map = map;
that = this;
}
//cwen渲染
Game.prototype.start = function() {
// 1.把食物和蛇渲染到页面
this.food.render(this.map);
this.snake.render(this.map);
// 2.游戏逻辑编写
//让蛇动起来
//判断地图边界
// runSnake();
//todo判断玩法(两种模式,原理一样)
goInput();
//通过键盘控制蛇头方向
//! keydown();
//蛇头碰到食物时处理
//在snake.js中判断
}
function goInput() {
var it = prompt('try:\n 经典玩法请按1\n 无敌玩法请输入(博主最帅)\n')
if (it == 1) {
runSnake();
keydown();
} else if (it == '博主最帅') {
runSnake1();
keydown1();
} else {
alert('you input could not be found!!!');
goInput();
}
}
//让蛇动起来
function runSnake() {
var timeId = setInterval(function() {
// var a = mark;
that.snake.move(that.food, that.map);
that.snake.render(that.map);
//判断地图边界
var maxX = (that.map.offsetWidth) / that.snake.width;
var maxY = (that.map.offsetHeight) / that.snake.height;
var headX = that.snake.kont[0].x;
var headY = that.snake.kont[0].y;
if (headX < 0 || headX >= maxX) {
alert('Game Over ' + '得分为 ' + that.snake.mark);
clearInterval(timeId);
} else if (headY < 0 || headY >= maxY) {
alert('Game Over ' + '成绩为 ' + that.snake.mark);
clearInterval(timeId);
}
}, 150)
}
//无敌版本蛇运动
function runSnake1() {
var timeId1 = setInterval(function() {
that.snake.move(that.food, that.map);
that.snake.render(that.map);
//判断地图边界
var maxX = (that.map.offsetWidth - that.snake.width) / that.snake.width;
var maxY = (that.map.offsetHeight - that.snake.height) / that.snake.height;
var headX = that.snake.kont[0].x;
var headY = that.snake.kont[0].y;
if (headX < 0) {
that.snake.kont[0].x = (that.map.offsetWidth - that.snake.width) / that.snake.width;
} else if (headX > maxX) {
that.snake.kont[0].x = 0;
} else if (headY < 0) {
that.snake.kont[0].y = (that.map.offsetHeight - that.snake.height) / that.snake.height;
} else if (headY > maxY) {
that.snake.kont[0].y = 0;
}
}, 50)
}
//通过键盘控制蛇头方向
function keydown() {
document.addEventListener('keydown', function(e) {
//通过事件对象判断按了哪个键 37left,38top,39right,40bottom
// console.log(e.keyCode);
//其在走的同时按下反方向无用
if (e.keyCode == 37 && that.snake.direction != 'right') {
that.snake.direction = 'left';
} else if (e.keyCode == 38 && that.snake.direction != 'bottom') {
that.snake.direction = 'top';
} else if (e.keyCode == 39 && that.snake.direction != 'left') {
that.snake.direction = 'right';
} else if (e.keyCode == 40 && that.snake.direction != 'top') {
that.snake.direction = 'bottom';
}
});
}
function keydown1() {
document.addEventListener('keydown', function(e) {
//通过事件对象判断按了哪个键 37left,38top,39right,40bottom
// console.log(e.keyCode);
//无敌版本四面八方任你行
if (e.keyCode == 37) {
that.snake.direction = 'left';
} else if (e.keyCode == 38) {
that.snake.direction = 'top';
} else if (e.keyCode == 39) {
that.snake.direction = 'right';
} else if (e.keyCode == 40) {
that.snake.direction = 'bottom';
}
});
}
//把Game开放
window.Game = Game;
})()
})
游戏启动与优化建议
第五步是通过main函数启动游戏。
window.addEventListener('load', function() {
(function(window, undefind) {
//测试
var map = document.querySelector('.map');
var game = new Game(map);
game.start();
})(window, undefined)
})
最后补充几点建议: * 建议将所有JavaScript代码合并至同一个JS文件中,这有助于提升页面加载速度。同时,务必在每个立即执行函数(IIFE)前添加分号“;”,以防止因代码合并导致的语法错误。
此外,小编还整理了JavaScript经典小游戏合集专题,供感兴趣的同学深入学习。
以上就是本文的全部内容,希望能为读者的学习提供有益帮助,欢迎大家继续支持本站。