案例目标
游戏的目的是用来体会js高级语法的使用 不需要具备抽象对象的能力,使用面向对象的方式分析问题,需要一个漫长的过程。
功能实现
搭建页面
放一个容器盛放游戏场景 div#map,设置样式
#map {
width: 800px;
height: 600px;
background-color: #ccc;
position: relative;
}
分析对象
- 游戏对象
- 蛇对象
- 食物对象
创建食物对象
-
Food
- 属性
- x
- y
- width
- height
- color
- 方法
- render 随机创建一个食物对象,并输出到map上
- 属性
-
创建Food的构造函数,并设置属性
var position = 'absolute';
var elements = [];
function Food(x, y, width, height, color) {
this.x = x || 0;
this.y = y || 0;
// 食物的宽度和高度(像素)
this.width = width || 20;
this.height = height || 20;
// 食物的颜色
this.color = color || 'green';
} -
通过原型设置render方法,实现随机产生食物对象,并渲染到map上
Food.prototype.render = function (map) {
// 随机食物的位置,map.宽度/food.宽度,总共有多少分food的宽度,随机一下。然后再乘以food的宽度
this.x = parseInt(Math.random() * map.offsetWidth / this.width) * this.width;
this.y = parseInt(Math.random() * map.offsetHeight / this.height) * this.height;// 动态创建食物对应的div var div = document.createElement('div'); map.appendChild(div); div.style.position = position; 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; elements.push(div);
}
-
通过自调用函数,进行封装,通过window暴露Food对象
window.Food = Food;
创建蛇对象
Snake
-
属性
- width 蛇节的宽度 默认20
- height 蛇节的高度 默认20
- body 数组,蛇的头部和身体,第一个位置是蛇头
- direction 蛇运动的方向 默认right 可以是 left top bottom
-
方法
- render 把蛇渲染到map上
-
Snake构造函数
var position = 'absolute';
var elements = [];
function Snake(width, height, direction) {
// 设置每一个蛇节的宽度
this.width = width || 20;
this.height = height || 20;
// 蛇的每一部分, 第一部分是蛇头
this.body = [
{x: 3, y: 2, color: 'red'},
{x: 2, y: 2, color: 'red'},
{x: 1, y: 2, color: 'red'}
];
this.direction = direction || 'right';
} -
render方法
Snake.prototype.render = function(map) {
for(var i = 0; i < this.body.length; i++) {
var obj = this.body[i];
var div = document.createElement('div');
map.appendChild(div);
div.style.left = obj.x * this.width + 'px';
div.style.top = obj.y * this.height + 'px';
div.style.position = position;
div.style.backgroundColor = obj.color;
div.style.width = this.width + 'px';
div.style.height = this.height + 'px';
}
} -
在自调用函数中暴露Snake对象
window.Snake = Snake;
创建游戏对象
游戏对象,用来管理游戏中的所有对象和开始游戏
- Game
- 属性
- food
- snake
- map
- 方法
- start 开始游戏(绘制所有游戏对象)
- 属性
-
构造函数
function Game(map) {
this.food = new Food();
this.snake = new Snake();
this.map = map;
} -
开始游戏,渲染食物对象和蛇对象
Game.prototype.start = function () {
this.food.render(this.map);
this.snake.render(this.map);
}
游戏的逻辑
写蛇的move方法
- 在蛇对象(snake.js)中,在Snake的原型上新增move方法
让蛇移动起来,把蛇身体的每一部分往前移动一下
-
蛇头部分根据不同的方向决定 往哪里移动
Snake.prototype.move = function (food, map) {
// 让蛇身体的每一部分往前移动一下
var i = this.body.length - 1;
for(; i > 0; i--) {
this.body[i].x = this.body[i - 1].x;
this.body[i].y = this.body[i - 1].y;
}
// 根据移动的方向,决定蛇头如何处理
switch(this.direction) {
case 'left':
this.body[0].x -= 1;
break;
case 'right':
this.body[0].x += 1;
break;
case 'top':
this.body[0].y -= 1;
break;
case 'bottom':
this.body[0].y += 1;
break;
}
}
-
在game中测试
this.snake.move(this.food, this.map);
this.snake.render(this.map);
让蛇自己动起来
私有方法
什么是私有方法?
不能被外部访问的方法
如何创建私有方法?
使用自调用函数包裹在game.js中 添加runSnake的私有方法,开启定时器调用蛇的move和render方法,让蛇动起来
-
判断蛇是否撞墙
function runSnake() {
var timerId = setInterval(function() {
this.snake.move(this.food, this.map);
// 在渲染前,删除之前的蛇
this.snake.render(this.map);// 判断蛇是否撞墙 var maxX = this.map.offsetWidth / this.snake.width; var maxY = this.map.offsetHeight / this.snake.height; var headX = this.snake.body[0].x; var headY = this.snake.body[0].y; if (headX < 0 || headX >= maxX) { clearInterval(timerId); alert('Game Over'); } if (headY < 0 || headY >= maxY) { clearInterval(timerId); alert('Game Over'); } }.bind(that), 150);
}
-
在snake中添加删除蛇的私有方法,在render中调用
function remove() {
// 删除渲染的蛇
var i = elements.length - 1;
for(; i >= 0; i--) {
// 删除页面上渲染的蛇
elements[i].parentNode.removeChild(elements[i]);
// 删除elements数组中的元素
elements.splice(i, 1);
}
} -
在game中通过键盘控制蛇的移动方向
function bindKey() {
document.addEventListener('keydown', function(e) {
switch (e.keyCode) {
case 37:
// left
this.snake.direction = 'left';
break;
case 38:
// top
this.snake.direction = 'top';
break;
case 39:
// right
this.snake.direction = 'right';
break;
case 40:
// bottom
this.snake.direction = 'bottom';
break;
}
}.bind(that), false);
} -
在start方法中调用
bindKey();
判断蛇是否吃到食物
// 在Snake的move方法中
// 在移动的过程中判断蛇是否吃到食物
// 如果蛇头和食物的位置重合代表吃到食物
// 食物的坐标是像素,蛇的坐标是几个宽度,进行转换
var headX = this.body[0].x * this.width;
var headY = this.body[0].y * this.height;
if (headX === food.x && headY === food.y) {
// 吃到食物,往蛇节的最后加一节
var last = this.body[this.body.length - 1];
this.body.push({
x: last.x,
y: last.y,
color: last.color
})
// 把现在的食物对象删除,并重新随机渲染一个食物对象
food.render(map);
}
完整代码:
1.html界面
2.tools.js-创建工具对象
/*创建工具对象--tools.js*/
var Tools = {
getRandom: function(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
}
}
3.food.js-创建食物对象
/*创建食物对象--Food.js*/
(function() {//自调用函数,避免变量冲突
var position = 'absolute';
var elements = [];
function Food(x, y, width, height, color) {
//3.1添加基本属性:背景颜色,width,height,x横坐标,y纵坐标
// 食物的颜色
this.color = color || 'green';
// 食物的宽度和高度(像素)
this.width = width || 20;
this.height = height || 20;
//x横坐标,y纵坐标
this.x = x || 0;
this.y = y || 0;
}
//通过原型设置render方法,实现随机产生食物对象,并渲染到map上
//样式初始化方法
Food.prototype.render = function(map) {
remove();//删除之前创建的食物
// 随机食物的位置,map.宽度/food.宽度,总共有多少分food的宽度,随机一下。然后再乘以food的宽度
this.x = Tools.getRandom(0, map.offsetWidth / this.width - 1) * this.width;
this.y = Tools.getRandom(0, map.offsetHeight / this.height - 1) * this.height;
// 动态创建食物对应的div
var div = document.createElement('div');
map.appendChild(div);
div.style.position = position;
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;
elements.push(div);
}
function remove() { //删除小蛇的私有方法
var i = elements.length - 1;
for(; i >= 0; i--) {
var ele = elements[i];
ele.parentElement.removeChild(ele);
elements.splice(i, 1);
}
}
//通过自调用函数,进行封装,通过window暴露Food对象
window.Food=Food;
}());
//测试
/* var map=document.getElementById("map");
var food=new Food();
food.render(map);
*/
4.snake.js-创建蛇对象
/*创建蛇对象--snake.js*/
(function() { //小蛇的自调用函数,避免变量冲突
var position = 'absolute';
var elements = []; //存放小蛇的每个身体部分-记录上一次创建的蛇节,为删除做准备
function Snake(options) { //小蛇的构造函数
options = options || {}; //options 如果有值返回options,如果没有值,返回{}
// 设置每一个蛇节的宽度,高度
this.width = options.width || 20;
this.height = options.height || 20;
// 蛇的每一部分的宽高颜色, 第一部分是蛇头
this.body = [{
x: 3,
y: 2,
color: 'red'
},
{
x: 2,
y: 2,
color: 'blue'
},
{
x: 1,
y: 2,
color: 'blue'
}
];
//蛇身移动的方向
this.direction = options.direction || 'right';
}
////通过原型设置render方法,实现蛇对象,并渲染到map上
Snake.prototype.render = function(map) {
remove();//删除之前创建的蛇节
//把每一个蛇节渲染在地图上
for(var i = 0; i < this.body.length; i++) { //循环遍历创建div
var obj = this.body[i]; //数组中每个数组元素都是一个对象
var div = document.createElement('div'); //创建div
map.appendChild(div); //把div加入到到map上
//设置div样式
elements.push(div);
div.style.left = obj.x * this.width + 'px';
div.style.top = obj.y * this.height + 'px';
div.style.position = position; //脱离文档流
div.style.backgroundColor = obj.color;
div.style.width = this.width + 'px';
div.style.height = this.height + 'px';
}
}
/*写蛇的move方法*/
/* 在蛇对象(snake.js)中,在Snake的原型上新增move方法
1. 让蛇移动起来,把蛇身体的每一部分往前移动一下
2. 蛇头部分根据不同的方向决定 往哪里移动*/
Snake.prototype.move = function(food, map) {
// 让蛇身体的每一部分往前移动一下
var i = this.body.length - 1;
for(; i > 0; i--) { //倒着来
this.body[i].x = this.body[i - 1].x; //每前进一步,自身i-1
this.body[i].y = this.body[i - 1].y;
}
// 根据移动的方向,决定蛇头如何处理
switch(this.direction) {
case 'left':
this.body[0].x -= 1;
break;
case 'right':
this.body[0].x += 1;
break;
case 'top':
this.body[0].y -= 1;
break;
case 'bottom':
this.body[0].y += 1;
break;
}
//判断有没有吃的食物
// 在移动的过程中判断蛇是否吃到食物
// 如果蛇头和食物的位置重合代表吃到食物
// 食物的坐标是像素,蛇的坐标是几个宽度,进行转换
var headX = this.body[0].x * this.width;
var headY = this.body[0].y * this.height;
if(headX === food.x && headY === food.y) {
// 吃到食物,往蛇节的最后加一节
var last = this.body[this.body.length - 1];
this.body.push({
x: last.x,
y: last.y,
color: last.color
})
// 把现在的食物对象删除,并重新随机渲染一个食物对象
food.render(map);
}
}
function remove() { //删除小蛇的私有方法
var i = elements.length - 1;
for(; i >= 0; i--) {
var ele = elements[i];
ele.parentElement.removeChild(ele);
elements.splice(i, 1);
}
}
//通过自调用函数,进行封装,通过window暴露Food对象
window.Snake = Snake;
})(); //调用()
/* //测试
var map=document.getElementById("map");
var snake=new Snake();
snake.render(map);*/
5.game.js-创建游戏对象
/*****游戏对象 game.js*****/
(function() { //自调用函数--游戏对象
var that; //该变量到目的是为了保存游戏Game的实例对象
function Game(map) { //游戏的构造函数
this.food = new Food(); //获取食物对象
this.Snake = new Snake(); //获取蛇对象
this.map = map; //获取地图
that = this; //保存当前实例对象到that变量中,此时的that就是this=Game对象
}
//开始游戏(初始化游戏)--可以设置小蛇和食物显示出来start
Game.prototype.start = function() {
this.food.render(this.map); //食物初始化-渲染到地图上
this.Snake.render(this.map); //蛇初始化-渲染到地图上
this.runSnake(this.food, this.map); //调用了小蛇自动移动的方法
this.bindkey();
};
/*什么是私有方法?
不能被外部访问的方法
如何创建私有方法?
使用自调用函数包裹*/
//在game.js中 添加runSnake的私有方法,开启定时器调用蛇的move和render方法,让蛇动起来
Game.prototype.runSnake = function( /*food, map*/ ) { //设置小蛇可以自动的跑起来
var timeId = setInterval(function() {
//移动小蛇 此时的that=this是window对象,不是Game对象
// 在渲染前,删除之前的蛇
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.body[0].x;
var headY = that.Snake.body[0].y;
if(headX < 0 || headX >= maxX) { //横坐标 :撞墙了,停止定时器
alert('Game Over');
clearInterval(timeId);
}
if(headY < 0 || headY >= maxY) { //纵坐标 :撞墙了,停止定时器
alert('Game Over');
clearInterval(timeId);
}
} /*bind(that)*/ , 200);
};
Game.prototype.bindkey = function() {//设置用户按键,改变小蛇移动的方向
document.addEventListener("keydown", function(event) {//获取用户的按键,改变小蛇移动的方向
switch(event.keyCode) {//获取按键的值
case 37:that.Snake.direction="left";
break;
case 38:that.Snake.direction="top";
break;
case 39:that.Snake.direction="right";
break;
case 40:that.Snake.direction="bottom";
break;
}
})
}
window.Game = Game;
})();
//测试
var game = new Game(document.getElementById("map")); //初始化游戏对象
game.start(); //开始游戏