游戏开发:小乌龟推箱子

作者: liufeisheng

创建时间: 2024-05-31 07:04:35


使用JavaScript技术,设计如下的一款小游戏:小乌龟推箱子。

为了减少代码量,可以使用jQuery库来实现 image.png


游戏介绍

游戏名称: 推箱子

游戏类型: 智力拼图

游戏目标: 玩家需要控制角色在迷宫中推动箱子,将它们放置到指定的位置来完成关卡。

游戏玩法说明

  1. 游戏界面:
  2. 游戏界面是一个由不同颜色的方块组成的网格迷宫。
  3. 蓝色方块代表可行走的空地。
  4. 灰色方块代表迷宫的墙壁,玩家和箱子都不能穿过。
  5. 红色方块代表目标位置,需要将箱子推到这里。

  6. 角色控制:

  7. 使用键盘上的箭头键来控制角色的移动:上(↑)、下(↓)、左(←)、右(→)。

  8. 箱子操作:

  9. 角色可以推动箱子,但不能拉动。
  10. 角色一次只能推动一个箱子。
  11. 箱子只能推动到相邻的空格上,如果目标位置被墙或其他箱子阻挡,则无法推动。

  12. 过关条件:

  13. 将所有的箱子都推到红色目标位置即为过关。
  14. 每个关卡的布局和难度都不同,需要玩家思考策略和移动路径。

  15. 游戏关卡:

  16. 游戏包含多个关卡,每个关卡都有不同的迷宫布局和挑战。
  17. 玩家需要通过智慧和策略来解决每一个关卡。

  18. 游戏特点:

  19. 推箱子是一款经典的智力游戏,它考验玩家的逻辑思维和空间想象力。
  20. 游戏简单易学,但要精通则需要不断尝试和思考。
  21. 每个关卡都有多种解法,玩家可以自由探索最优路径。

游戏关卡数据

以下是游戏关卡数据,可以直接在游戏中复制引用

pass : [    //每一关的数据
                {
                    maps : [
                        1,1,3,3,3,3,1,1,                        
                        1,1,3,2,2,3,1,1,
                        1,3,3,0,2,3,3,1,
                        1,3,0,0,0,2,3,1,
                        3,3,0,0,0,0,3,3,
                        3,0,0,3,0,0,0,3,
                        3,0,0,0,0,0,0,3,
                        3,3,3,3,3,3,3,3
                    ],
                    cols : 8,
                    boxs : [
                        { x : 4 , y : 3 },
                        { x : 3 , y : 4 },
                        { x : 4 , y : 5 },
                        { x : 5 , y : 5 }
                    ],
                    person : { x : 3 , y : 6 }
                },
                {
                    maps : [
                        1,1,1,1,3,3,3,3,3,3,3,1,
                        1,1,1,1,3,0,0,3,0,0,3,1,
                        1,1,1,1,3,0,0,0,0,0,3,1,
                        3,3,3,3,3,0,0,3,0,0,3,1,
                        2,2,2,3,3,3,0,3,0,0,3,3,
                        2,0,0,3,0,0,0,0,3,0,0,3,
                        2,0,0,0,0,0,0,0,0,0,0,3,
                        2,0,0,3,0,0,0,0,3,0,0,3,
                        2,2,3,3,3,3,0,3,0,0,3,3,
                        3,3,3,3,3,0,0,0,0,0,3,1,
                        1,1,1,1,3,0,0,3,0,0,3,1,
                        1,1,1,1,3,3,3,3,3,3,3,1

                    ],
                    cols : 12,
                    boxs : [
                        {x : 5 , y : 6},
                        {x : 6 , y : 3},
                        {x : 6 , y : 5},
                        {x : 6 , y : 7},
                        {x : 6 , y : 9},
                        {x : 7 , y : 2},
                        {x : 8 , y : 2},
                        {x : 9 , y : 6}
                    ],
                    person : { x : 5 , y : 9 }
                }

            ]

游戏设计

首先一个名为 game 的对象,包含了推箱子游戏的主要功能。以下是 game 对象中给出以下各个功能函数:

  1. init() - 初始化函数:
  2. 调用其他函数来设置游戏的初始状态,包括元素的创建、地图的生成、箱子和角色的创建以及绑定键盘操作。

  3. resetState() - 重置状态函数:

  4. 清空游戏界面并移除键盘事件监听,以便重新初始化游戏。

  5. elements() - 元素接收函数:

  6. 设置游戏所需的元素和数据,如游戏地图、列数、箱子位置、角色位置等。

  7. createMap() - 创建地图函数:

  8. 根据 maps 数组生成游戏地图的HTML元素,每个元素对应地图上的一个格子。

  9. createBox() - 创建箱子函数:

  10. 根据 boxs 数组生成箱子的HTML元素,设置箱子的初始位置。

  11. createPerson() - 创建人物函数:

  12. 创建并设置玩家角色的HTML元素,包括其初始位置。

  13. bindPerson() - 绑定人物操作函数:

  14. 绑定键盘事件,根据玩家按键操作来控制角色移动。

  15. movePerson(opts) - 移动人物函数:

  16. 根据传入的选项(如 {x: -1, y: 0} 表示向左移动)来更新角色的位置,并检查是否撞墙或推动箱子。

  17. isWall(opts) - 判断是否是墙函数:

  18. 检查角色尝试移动的方向是否有墙壁阻挡。

  19. moveBox(opts) - 移动箱子函数:

  20. 检查角色是否推动箱子,并相应地更新箱子的位置。同时检查是否有其他箱子阻碍移动。

  21. isNextPass() - 是否进入下一关函数:

  22. 检查所有箱子是否都已推到目标位置,如果是,则允许玩家进入下一关。

  23. pz($elem1, $elem2) - 碰撞检测函数:

  24. 检测两个元素(如角色和箱子或箱子和目标位置)是否发生碰撞。

这些函数共同工作,提供了推箱子游戏的核心功能,包括游戏初始化、界面渲染、玩家输入处理、游戏逻辑判断和关卡管理等。

其中游戏地图是由基本数字生成的,每一关的地图都是不一样的。使地图形成网格,数组的每一个元素就是地图元素。

操作步骤

第一部分内容:搭建游戏界面,静态部分

第1次课课堂实现

首先搭建文件框架,将html、css、js等文件导入,加入游戏基本说明之类的内容。 把jquery\图片等元素放到一个项目文件中。

image.png 效果如下

image.png

  1. init() - 初始化函数:
  2. 调用其他函数来设置游戏的初始状态,包括元素的创建、地图的生成、箱子和角色的创建以及绑定键盘操作。 在JS中创建以上游戏数据,然后设置初始关卡now和网格大小gridSize ,设置初始游戏方法

image.png

  1. 设置游戏样式

image.png

  1. elements() - 元素接收函数:
  2. 设置游戏所需的元素和数据,如游戏地图、列数、箱子位置、角色位置等。

image.png

  1. createMap() - 创建地图函数:
  2. 根据 maps 数组生成游戏地图的HTML元素,每个元素对应地图上的一个格子。

image.png

  1. 细节调整
  2. js代码引用,需要放在body里面

image.png - css调整,游戏信息说明放在游戏界面之下

image.png

image.png

目前效果图如下 image.png

  1. createBox() - 创建箱子函数:
  2. 根据 boxs 数组生成箱子的HTML元素,设置箱子的初始位置。

image.png

  1. createPerson() - 创建人物函数:
  2. 创建并设置玩家角色的HTML元素,包括其初始位置。 image.png

目前游戏界面已经搭建好了,如下图所示:

image.png

以下是到目前为止的相关代码

html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>小乌龟推箱子游戏开发</title>
    <script src="jquery-3.7.1.min.js"></script>
    <link rel="stylesheet" href="game.css">
</head>
<body>
    <h1>小乌龟推箱子游戏开发</h1>
    <hr>
    <div id="main"></div>
    <div id="info">
        游戏名称: 推箱子<br>

游戏类型: 智力拼图<br>

游戏目标: <br>
玩家需要控制角色在迷宫中推动箱子,将它们放置到指定的位置来完成关卡。

<hr>
游戏玩法说明<br>
游戏界面:<br>

游戏界面是一个由不同颜色的方块组成的网格迷宫。<br>
蓝色方块代表可行走的空地。<br>
灰色方块代表迷宫的墙壁,玩家和箱子都不能穿过。<br>
红色方块代表目标位置,需要将箱子推到这里。<hr>
角色控制:<br>

使用键盘上的箭头键来控制角色的移动:上(↑)、下(↓)、左(←)、右(→)。
箱子操作:<br>

角色可以推动箱子,但不能拉动。
角色一次只能推动一个箱子。
箱子只能推动到相邻的空格上,如果目标位置被墙或其他箱子阻挡,则无法推动。<hr>
过关条件:<br>

将所有的箱子都推到红色目标位置即为过关。
每个关卡的布局和难度都不同,需要玩家思考策略和移动路径。<hr>
游戏关卡:<br>

游戏包含多个关卡,每个关卡都有不同的迷宫布局和挑战。
玩家需要通过智慧和策略来解决每一个关卡。<hr>
游戏特点:<br>

推箱子是一款经典的智力游戏,它考验玩家的逻辑思维和空间想象力。
游戏简单易学,但要精通则需要不断尝试和思考。
每个关卡都有多种解法,玩家可以自由探索最优路径。

    </div>
    <script src="game.js"></script>
</body>
</html>

css

*{
    margin: 0;
    padding: 0;
}
#main{
    margin: 20px auto;
    position: relative;
}
#main div{ width: 50px; height: 50px; float: left; }
.pos0{background: blue;}
.pos1{background: gray;}
.pos2{background: red;}
.pos3{background: url(img/wall.png);}
.box{
    position: absolute;
    background: url(img/box.png);
}
.person{
    position: absolute;
    background: url(img/person.png);
}
#info{
    float: left;
}

js代码


var game = {
    pass : [    //每一关的数据
        {
            maps : [
                1,1,3,3,3,3,1,1,                        
                1,1,3,2,2,3,1,1,
                1,3,3,0,2,3,3,1,
                1,3,0,0,0,2,3,1,
                3,3,0,0,0,0,3,3,
                3,0,0,3,0,0,0,3,
                3,0,0,0,0,0,0,3,
                3,3,3,3,3,3,3,3
            ],
            cols : 8,
            boxs : [
                { x : 4 , y : 3 },
                { x : 3 , y : 4 },
                { x : 4 , y : 5 },
                { x : 5 , y : 5 }
            ],
            person : { x : 3 , y : 6 }
        },
        {
            maps : [
                1,1,1,1,3,3,3,3,3,3,3,1,
                1,1,1,1,3,0,0,3,0,0,3,1,
                1,1,1,1,3,0,0,0,0,0,3,1,
                3,3,3,3,3,0,0,3,0,0,3,1,
                2,2,2,3,3,3,0,3,0,0,3,3,
                2,0,0,3,0,0,0,0,3,0,0,3,
                2,0,0,0,0,0,0,0,0,0,0,3,
                2,0,0,3,0,0,0,0,3,0,0,3,
                2,2,3,3,3,3,0,3,0,0,3,3,
                3,3,3,3,3,0,0,0,0,0,3,1,
                1,1,1,1,3,0,0,3,0,0,3,1,
                1,1,1,1,3,3,3,3,3,3,3,1

            ],
            cols : 12,
            boxs : [
                {x : 5 , y : 6},
                {x : 6 , y : 3},
                {x : 6 , y : 5},
                {x : 6 , y : 7},
                {x : 6 , y : 9},
                {x : 7 , y : 2},
                {x : 8 , y : 2},
                {x : 9 , y : 6}
            ],
            person : { x : 5 , y : 9 }
        }

    ],
    now : 0,   //初始关卡
    girdSize : 50,  //网格大小
    init : function(){   //初始化
        this.elements();
        this.resetState();
        this.createMap();
        this.createBox();
        this.createPerson();
        this.bindPerson();
    },
    resetState : function(){   //还原初始状态
        this.$main.empty();
        $(document).off('keydown');
    },
    elements : function(){   //接收元素和数据
        this.$main = $('#main');
        this.$person = null;
        this.$box = null;
        this.$pos2 = null;
        this.maps = this.pass[this.now].maps;
        this.cols = this.pass[this.now].cols;
        this.boxs = this.pass[this.now].boxs;
        this.person = this.pass[this.now].person;
    },
    createMap : function(){   //创建地图
        this.$main.css('width', this.cols * this.girdSize );
        $.each(this.maps,$.proxy(function(i,v){
            var $div = $('<div>');
            $div.attr('class','pos'+v);
            this.$main.append($div);
        },this));
        this.$pos2 = this.$main.find('.pos2');
    },
    createBox : function(){   //创建箱子
        $.each(this.boxs,$.proxy(function(i,v){
            var $div = $('<div>');
            $div.attr('class','box');
            $div.css('left' , v.x * this.girdSize);
            $div.css('top' , v.y * this.girdSize);
            this.$main.append($div);
        },this));
        this.$box = this.$main.find('.box');
    },
    createPerson : function(){   //创建人物
        var $div = $('<div>');
        $div.attr('class','person');
        $div.css('left',this.person.x * this.girdSize);
        $div.css('top',this.person.y * this.girdSize);
        this.$main.append($div);
        this.$person = $div;
    }


};

game.init();


但目前仅仅是静止状态,后面将要让游戏角色及界面动起来

第二部分:操作角色,让游戏界面动起来

第2次课内容

2.1 bindPerson() - 绑定人物操作函数:

  • 绑定键盘事件,根据玩家按键操作来控制角色移动。

image.png

image.png 2.2 movePerson(opts) - 移动人物函数: - 根据传入的选项(如 {x: -1, y: 0} 表示向左移动)来更新角色的位置,并检查是否撞墙或推动箱子。

image.png 2.3 isWall(opts) - 判断是否是墙函数: - 检查角色尝试移动的方向是否有墙壁阻挡。

image.png 2.4 moveBox(opts) - 移动箱子函数: - 检查角色是否推动箱子,并相应地更新箱子的位置。同时检查是否有其他箱子阻碍移动。

image.png 2.5 isNextPass() - 是否进入下一关函数: - 检查所有箱子是否都已推到目标位置,如果是,则允许玩家进入下一关。

image.png 2.6 pz(elem1,elem2) - 碰撞检测函数: - 检测两个元素(如角色和箱子或箱子和目标位置)是否发生碰撞

image.png

最终的js代码


var game = {
    pass : [    //每一关的数据
        {
            maps : [
                1,1,3,3,3,3,1,1,                        
                1,1,3,2,2,3,1,1,
                1,3,3,0,2,3,3,1,
                1,3,0,0,0,2,3,1,
                3,3,0,0,0,0,3,3,
                3,0,0,3,0,0,0,3,
                3,0,0,0,0,0,0,3,
                3,3,3,3,3,3,3,3
            ],
            cols : 8,
            boxs : [
                { x : 4 , y : 3 },
                { x : 3 , y : 4 },
                { x : 4 , y : 5 },
                { x : 5 , y : 5 }
            ],
            person : { x : 3 , y : 6 }
        },
        {
            maps : [
                1,1,1,1,3,3,3,3,3,3,3,1,
                1,1,1,1,3,0,0,3,0,0,3,1,
                1,1,1,1,3,0,0,0,0,0,3,1,
                3,3,3,3,3,0,0,3,0,0,3,1,
                2,2,2,3,3,3,0,3,0,0,3,3,
                2,0,0,3,0,0,0,0,3,0,0,3,
                2,0,0,0,0,0,0,0,0,0,0,3,
                2,0,0,3,0,0,0,0,3,0,0,3,
                2,2,3,3,3,3,0,3,0,0,3,3,
                3,3,3,3,3,0,0,0,0,0,3,1,
                1,1,1,1,3,0,0,3,0,0,3,1,
                1,1,1,1,3,3,3,3,3,3,3,1

            ],
            cols : 12,
            boxs : [
                {x : 5 , y : 6},
                {x : 6 , y : 3},
                {x : 6 , y : 5},
                {x : 6 , y : 7},
                {x : 6 , y : 9},
                {x : 7 , y : 2},
                {x : 8 , y : 2},
                {x : 9 , y : 6}
            ],
            person : { x : 5 , y : 9 }
        }

    ],
    now : 0,   //初始关卡
    girdSize : 50,  //网格大小
    init : function(){   //初始化
        this.elements();
        this.resetState();
        this.createMap();
        this.createBox();
        this.createPerson();
        this.bindPerson();
    },
    resetState : function(){   //还原初始状态
        this.$main.empty();
        $(document).off('keydown');
    },
    elements : function(){   //接收元素和数据
        this.$main = $('#main');
        this.$person = null;
        this.$box = null;
        this.$pos2 = null;
        this.maps = this.pass[this.now].maps;
        this.cols = this.pass[this.now].cols;
        this.boxs = this.pass[this.now].boxs;
        this.person = this.pass[this.now].person;
    },
    createMap : function(){   //创建地图
        this.$main.css('width', this.cols * this.girdSize );
        $.each(this.maps,$.proxy(function(i,v){
            var $div = $('<div>');
            $div.attr('class','pos'+v);
            this.$main.append($div);
        },this));
        this.$pos2 = this.$main.find('.pos2');
    },
    createBox : function(){   //创建箱子
        $.each(this.boxs,$.proxy(function(i,v){
            var $div = $('<div>');
            $div.attr('class','box');
            $div.css('left' , v.x * this.girdSize);
            $div.css('top' , v.y * this.girdSize);
            this.$main.append($div);
        },this));
        this.$box = this.$main.find('.box');
    },
    createPerson : function(){   //创建人物
        var $div = $('<div>');
        $div.attr('class','person');
        $div.css('left',this.person.x * this.girdSize);
        $div.css('top',this.person.y * this.girdSize);
        this.$main.append($div);
        this.$person = $div;
    },
    // bindPerson() - 绑定人物操作函数:
    // 绑定键盘事件,根据玩家按键操作来控制角色移动。
    bindPerson: function(){
        $(document).on("keydown",$.proxy(function(ev){
            switch(ev.keyCode){
                case 37:  // 左  
                case 65:  // a
                    this.$person.css('backgroundPosition',"-150px 0");
                    this.movePerson({x:-1,y:0});
                    console.log("左");
                    break;
                case 38:  // 上
                case 87:  // w
                    this.$person.css("backgroundPosition","0 0");
                    this.movePerson({x:0,y:-1});
                    break;
                case 39:  // 右
                case 87:  // d
                    this.$person.css("backgroundPosition","-50px 0");
                    this.movePerson({x:1,y:0});
                    break;
                case 40:  // 下
                case 83:  // s
                    this.$person.css("backgroundPosition","-100px 0");
                    this.movePerson({x:0,y:1});
                    break;
            }
        },this));
    },

    // movePerson(opts) - 移动人物函数:
    // 根据传入的选项(如 {x: -1, y: 0} 表示向左移动)来更新角色的位置,并检查是否撞墙或推动箱子。   
    movePerson : function(opts){   //移动人物
        if( !this.isWall(opts) ){
            this.person.x += opts.x;
            this.person.y += opts.y;
            this.$person.css('left',this.person.x * this.girdSize);
            this.$person.css('top',this.person.y * this.girdSize);
        }
        this.moveBox(opts);
        if( this.isNextPass() ){
            this.now++;
            this.init();
        }
    },
    // isWall(opts) - 判断是否是墙函数:
    // 检查角色尝试移动的方向是否有墙壁阻挡。
    isWall:function(opts){
        var num = this.maps[(this.person.y+opts.y)*this.cols+(this.person.x+opts.x)];
        return num == 3?true:false;
    },
    // moveBox(opts) - 移动箱子函数:
    // 检查角色是否推动箱子,并相应地更新箱子的位置。同时检查是否有其他箱子阻碍移动。
    moveBox : function(opts){    //移动箱子
        this.$box.each($.proxy(function(i,elem){
            if( this.pz( this.$person , $(elem) ) && !this.isWall(opts) ){
                $(elem).css('left' , (this.person.x + opts.x)*this.girdSize);
                $(elem).css('top' , (this.person.y + opts.y)*this.girdSize);
                this.$box.each($.proxy(function(j,elem2){ 
                    //判断两个箱子是否挨着
                    if( this.pz( $(elem) , $(elem2) ) && elem != elem2 ){
                        $(elem).css('left' , this.person.x*this.girdSize);
                        $(elem).css('top' , this.person.y*this.girdSize);
                        this.person.x -= opts.x;
                        this.person.y -= opts.y;
                        this.$person.css('left' , this.person.x * this.girdSize);
                        this.$person.css('top' , this.person.y * this.girdSize);
                    }   
                },this));
            }
            else if( this.pz( this.$person , $(elem) ) ){
                this.person.x -= opts.x;
                this.person.y -= opts.y;
                this.$person.css('left' , this.person.x * this.girdSize);
                this.$person.css('top' , this.person.y * this.girdSize);
            }
        },this));
    },
    // isNextPass() - 是否进入下一关函数:
    // 检查所有箱子是否都已推到目标位置,如果是,则允许玩家进入下一关。
    isNextPass:function(){
        var count = 0;
        this.$box.each($.proxy(function(i,elem){
            this.$pos2.each($.proxy(function(j,elem2){
                if(this.pz($(elem),$(elem2))){
                    count++;
                }
            },this));
        },this));
        if(count == this.$box.length){
            return true;
        }else 
            return false;
    },    
    // pz(elem1,elem2) - 碰撞检测函数:
    // 检测两个元素(如角色和箱子或箱子和目标位置)是否发生碰撞
    pz: function($elem1,$elem2){
        var L1 = $elem1.offset().left;
        var R1 = $elem1.offset().left+$elem1.width();
        var T1 = $elem1.offset().top;
        var B1 = $elem1.offset().top+$elem1.height();
        var L2 = $elem2.offset().left;
        var R2 = $elem2.offset().left+$elem2.width();
        var T2 = $elem2.offset().top;
        var B2 = $elem2.offset().top+$elem2.height();
        if(R1<=L2||L1>=R2||B1<=T2||T1>=B2){
            return false;
        }else 
            return true;
    }

};

game.init();


游戏扩展

针对这款游戏,还可以扩展其他功能,比如: - 添加一些不同功能的按钮。如“下一关”、“重置游戏”等。 - 增添角色操作步骤、游戏运行时间显示。 - 加入充值服务,免费玩第一关,第二关开始每玩一次充1个游戏币 - 通关奖励游戏币