最近项目上线, 近一个星期没更博了, 今天来写一个经典的游戏案例 -- 贪吃蛇. 在这个简单的案例里可以体会 javaScript 面向对象开发相关模式, 学习使用面向对象的方式分析问题.
1. 功能实现
1.1 搭建页面: 放一个容器盛放游戏场景 div#map, 设置样式
- <div class="map" id="map"></div>
- <style>
- #map{
- background-color: #000;
- width: 1500px;
- height: 700px;
- position: relative;
- left: 0;
- top: 0;
- }
- </style>
1.2 分析对象: 食物对象, 蛇对象, 游戏对象
1.3 创建食物对象 Food
属性: 位置(x,y), 大小(width,height), 颜色(color)
- // 创建 Food 的构造函数, 并设置属性
- function Food(width,height,bgColor) {
- // 食物的宽度和高度(像素)
- this.width=width||10;
- this.height=height||10;
- // 食物的颜色
- this.bgColor=bgColor||"white";
- }
方法: render() 随机创建一个食物对象, 并输出到 map 上
- // 通过原型设置 render 方法, 实现随机产生食物对象, 并渲染到 map 上
- Food.prototype.render=function (map) {
- remove(map);
- // 随机食物的位置, map. 宽度 / food. 宽度, 总共有多少分 food 的宽度, 随机一下. 然后再乘以 food 的宽度
- this.x=Math.floor(Math.random()*(map.offsetWidth/this.width))*this.width;
- this.y=Math.floor(Math.random()*(map.offsetHeight/this.height))*this.height;
- // 动态创建食物对应的 div
- var newDiv=document.createElement("div");
- newDiv.style.position="absolute";
- newDiv.style.left=this.x+"px";
- newDiv.style.top=this.y+"px";
- newDiv.style.backgroundColor=this.bgColor;
- newDiv.style.width=this.width+"px";
- newDiv.style.height=this.height+"px";
- map.appendChild(newDiv);
- li.push(newDiv);
- }
1.4 创建蛇对象 Snake
属性: 大小(width,height), 颜色(color), 方向(direction), 身体数组对象(body)
- // Snake 构造函数
- function Snake(width,height,bgColor,direction) {
- // 设置每一个蛇节的宽度
- this.width=width||10;
- this.height=height||10;
- this.bgColor=bgColor||"white";
- // 蛇的运动方向
- this.direction=direction||"right";
- // 蛇的每一部分, 第一部分是蛇头
- this.body=[
- {x:3,y:1},
- {x:2,y:1},
- {x:1,y:1}
- ];
- }
方法: render() 把蛇渲染到 map 上
- // render 方法, 原理与渲染食物相同
- Snake.prototype.render=function (map) {
- remove(map);
- for (var i = 0; i <this.body.length; i++) {
- var newDiv=document.createElement("div");
- newDiv.style.position="absolute";
- newDiv.style.left=this.body[i].x*this.width+"px";
- newDiv.style.top=this.body[i].y*this.height+"px";
- newDiv.style.width=this.width+"px";
- newDiv.style.height=this.height+"px";
- newDiv.style.backgroundColor=this.bgColor;
- map.appendChild(newDiv);
- list.push(newDiv);
- }
- }
1.5 创建游戏对象 Game(用来管理游戏中的所有对象和开始游戏)
属性: food,snake,map
- // Game 构造函数
- function Game(map) {
- this.map=map;
- this.snake=new Snake();
- this.food=new Food();
- that=this;
- }
方法: start() 开始游戏(绘制所有游戏对象)
- // 开始游戏, 渲染食物对象和蛇对象
- Game.prototype.startGame=function () {
- this.food.render(this.map);
- this.snake.render(this.map);
- autoMove();
- keyBind();
- }
- // 在自调用函数中暴露 Game 对象
- window.Game=Game;
2. 游戏逻辑
2.1 蛇的 move 方法
在蛇对象 (snake.js) 中, 在 Snake 的原型上新增 move 方法
让蛇移动起来, 把蛇身体的每一部分往前移动一下
蛇头部分根据不同的方向决定 往哪里移动
- Snake.prototype.move=function (food,map) {
- // 让蛇身体的每一部分往前移动一下
- for (var i = this.body.length-1; 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--;
- break;
- case "right":
- this.body[0].x++;
- break;
- case "up":
- this.body[0].y--;
- break;
- case "down":
- this.body[0].y++;
- break;
- default:
- break;
- }
- }
- // 在 game 中测试
- this.snake.move(this.food, this.map);
- this.snake.render(this.map);
2.2 让蛇自己动起来
在 game.js 中 添加 autoMove 的私有方法, 开启定时器调用蛇的 move 和 render 方法, 让蛇动起来(私有方法即不能被外部访问的方法, 使用自调用函数包裹)
- function autoMove() {
- var timeId=setInterval(function () {
- this.snake.move(this.food,this.map);
- this.snake.render(this.map);
- // 判断蛇是否撞墙
- var snakeHeadX=this.snake.body[0].x*this.snake.width;
- var snakeHeadY=this.snake.body[0].y*this.snake.width;
- if(snakeHeadX<0 || snakeHeadY<0 || snakeHeadX>=this.map.offsetWidth || snakeHeadY>=this.map.offsetHeight){
- clearInterval(timeId);
- alert("Game over!");
- }
- }.bind(that),50);
- }
在 snake 中添加删除蛇的私有方法, 在 render 中调用
- function remove(map) {
- for (var i = 0; i < list.length; i++) {
- map.removeChild(list[i]);
- }
- list.length=0;
- }
在 game 中通过键盘控制蛇的移动方向
- function keyBind() {
- window.onkeydown=function (e) {
- e=e||window.event;
- e.keyCode= e.keyCode|| e.charCode|| e.which;
- // console.log(e.keyCode);
- switch (e.keyCode) {
- case 37:
- if(this.snake.direction!="right"){
- this.snake.direction="left";
- }
- break;
- case 38:
- if (this.snake.direction != "down") {
- this.snake.direction = "up";
- }
- break;
- case 39:
- if (this.snake.direction != "left") {
- this.snake.direction = "right";
- }
- break;
- case 40:
- if (this.snake.direction != "up") {
- this.snake.direction = "down";
- }
- break;
- default:
- break;
- }
- }.bind(that);
- }
在 start 方法中调用 keyBind()
2.3 判断蛇是否吃到食物
在 Snake 的 move 方法中添加判断
- // 在移动的过程中判断蛇是否吃到食物
- var snakeHeadX=this.body[0].x*this.width;
- var snakeHeadY=this.body[0].y*this.height;
- var snakeTile=this.body[this.body.length-1];
- // 如果蛇头和食物的位置重合代表吃到食物
- if(snakeHeadX==food.x && snakeHeadY==food.y){
- // 吃到食物, 往蛇节的最后加一节
- this.body.push({
- // 食物的坐标是像素, 蛇的坐标是几个宽度, 进行转换
- x:snakeTile.x,
- y:snakeTile.y
- });
- // 把现在的食物对象删除, 并重新随机渲染一个食物对象
- food.render(map);
- }
自调用函数的参数
- (function (window, undefined) {
- var document = window.document;
- }(window, undefined))
传入 window 对象: 代码压缩的时候, 可以把 function (window) 压缩成 function (w)
传入 undefined: 把 undefined 作为函数的参数(当前案例没有使用) , 防止 undefined 被重新赋值, 因为在有的老版本的浏览器中 undefined 可以被重新赋值
关于自调用函数的问题
如果存在多个自调用函数要用分号分割, 否则语法错误
- // 下面代码会报错
- (function () {
- }())
- (function () {
- }())
- // 所以代码规范中会建议在自调用函数之前加上分号
- // 下面代码没有问题
- ;(function () {
- }())
- ;(function () {
- }())
当自调用函数前面有函数声明时, 会把自调用函数作为参数
- // 所以建议自调用函数前, 加上;
- var a = function () {
- alert('11');
- }
- (function () {
- alert('22');
- }())
来源: http://www.bubuko.com/infodetail-2714859.html