面向对象游戏案例:贪吃蛇

案例目标

游戏的目的是用来体会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方法
  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;
    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界面

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <title></title>
        <style type="text/css">
            #map{
                width: 800px;
                height: 600px;
                background-color: #ccc;
                position: relative;
            }
        </style>
    </head>
    <body>
        <div id="map"></div>
        <script type="text/javascript" src="js/tools.js"></script>
        <script type="text/javascript" src="js/food.js"></script>
        <script type="text/javascript" src="js/snake.js"></script>
        <script type="text/javascript" src="js/game.js" ></script>
    </body>
</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(); //开始游戏
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容