js之最原始版贪吃蛇的制作

贪吃蛇是一个特别古老的手机游戏了,诺基亚“老年机”上自带的游戏,现在自己尝试弄一个,来捡起来丢了几个月的creator。

1、创建蛇身体的单元。

虽然蛇看起来一长条,但是还是一个个单元连接起来的,所以我们先创建一个蛇单元,右击canvas—>创建节点—>创建渲染节点—>Sprite(单色),改变大小为40x40(因为Canvas的大小为1280x720,所以选了可以同时被整除的40),锚点改为(0,0)是为了方便计算。不过后来我将这个sprite添加到预制体了。
js之最原始版贪吃蛇的制作_第1张图片

2、创建脚本Sanke.js

在脚本属性里面添加预制体和存储蛇的节点等各种需要的属性:

properties: {
     //存储蛇的节点
    Snake: {
        default: null,
        type: cc.Node
    },
    //蛇的身体组件预制体
    prefab_snake: {
        default: null,
        type: cc.Prefab
    },
    //蛇的食物
    prefab_food: {
        default: null,
        type: cc.Prefab
    },
    //游戏结束弹窗
    prefab_GameOver: {
        default: null,
        type: cc.Node
    },
    //分数
    txt_score: {
        default: null,
        type: cc.Label
    },
    //网格单位长宽
    gridWidthAndHeight: 40,
    //蛇可以单次位移的距离
    distance: 40,
    //的单位高度
    snakeHeight: 40,
    //的单位宽度
    snakeWidth: 40,
    //屏幕横向最多单位数
    landscapeGridNum: 32,
    //屏幕竖向最多单位数
    verticalGridNum: 18,
    //当跑的方向(1:上;2:下;3:左;4:右)
    snakeDirection: 4,
    //上次跑的方向
    lastSnakeDirection: 4
},

3、直接创建蛇:

蛇在游戏开始时的方向一般默认为向右,这里创建长度为五节的向右的蛇,代码如下:

init(){
    //循环五次
    for (let i = 1; i < 6; i++) {
        this.addSnakeLength(i);
    }
}
/**
 * 初始的时候增加的长度
 * @param {*} num 初始化的的位置
 */
addSnakeLength(num) {
    var snakeNode = cc.instantiate(this.prefab_snake);
    snakeNode.parent = this.Snake;
    snakeNode.x = - num * snakeNode.width;
    snakeNode.y = 0 * snakeNode.height;
    this.snake_Arr.push(snakeNode);
    this.snake_pos_Arr.push({ x: - num, y: 0 });
},

其中,snake_Arr存储蛇身体的节点,snake_pos_arr存储蛇身体每个节点的网格坐标(为了方便计算,所以不直接用像素,而是将屏幕分为长宽均为40px的像素格子,格子本身不存在,只是计算的时候用,这样蛇的坐标可以简化为(0,0),(5,2)这种的,真实位置就是乘以40)。
这样,一条五长度的蛇就创建完成,如下:
js之最原始版贪吃蛇的制作_第2张图片

4、创建蛇的食物(上图中的红色块):

因为蛇的食物只有一块,所以在游戏开始的时候生成一次,之后如果被吃了随机换个位置就好,并且随机的位置不能和蛇本身重合。
代码如下:

/**
* 创建的食物
*/
createFood() {
    var foodPos = this.createFoodPos();
    console.log("随机生成的位置 == ", foodPos);
    var food = cc.instantiate(this.prefab_food);
    food.position = cc.v2(foodPos.x * this.gridWidthAndHeight, foodPos.y * this.gridWidthAndHeight);
    food.parent = this.node;
    this.Food = food;
},
 /**
 * 创建的食物坐标
 */
createFoodPos() {
    var foodPos_X = Math.floor(Math.random() * this.landscapeGridNum) - this.landscapeGridNum / 2;
    var foodPos_Y = Math.floor(Math.random() * this.verticalGridNum) - this.verticalGridNum / 2;
    var foodPos = { x: foodPos_X, y: foodPos_Y };
    //创建的时候需要比较下是不是和本身重叠了,重叠了需要重新生成
    for (let i = 0; i < this.snake_pos_Arr.length;) {
        if (this.snake_pos_Arr[i].x == foodPos.x && this.snake_pos_Arr[i].y == foodPos.y) {
            this.createFoodPos();
            break;
        } else {
            i++;
        }
        if (i == this.snake_pos_Arr.length) {
            this.FoodPos = foodPos;
            return foodPos;
        }
    } },

5、蛇的运动

蛇和食物都有了,接下来就是蛇的运动了,因为蛇是一节一节的,每一节对应一个坐标,而蛇运动的特点就是除了蛇头,蛇身上别的一节,都会走到前一节的位置上,所以代码原理就是在创建蛇的时候,将蛇身坐标存储到数组里,然后依据蛇的运动方向,在数组头部添加一个新的坐标,去除掉最后一个坐标,然后蛇的每个节点一一对应新数组的坐标位置,即可完成蛇身移动,代码如下:
假如蛇的运动方向为上(实际上需要四个方向判断),则:

	//蛇头上移一个网格的像素
    this.snake_Arr[0].y += this.distance;
    //给记录蛇的坐标数组首位添加新坐标;
    this.snake_pos_Arr.unshift({ x: this.snake_Arr[0].x / this.snakeWidth, y: this.snake_Arr[0].y / this.snakeHeight });
    //删掉最后一个坐标
    this.snake_pos_Arr.pop();

然后蛇的身体部分运动:

for (let i = 0; i < this.snake_Arr.length; i++) {
    if (i == 0) {
    } else {
        this.snake_Arr[i].x = this.snake_pos_Arr[i].x * this.snakeWidth;
        this.snake_Arr[i].y = this.snake_pos_Arr[i].y * this.snakeHeight;
    }
}

6、死亡检测

蛇会在碰到自己身体的时候死亡,因为蛇全身的坐标被保存在一个数组里,如果蛇撞到了自身,则该数组会出现蛇头位置坐标和某个坐标重复,检测数组是否有和首位元素重复的元素即可判断蛇是否自杀:

//判断是不是碰撞到自己了
crashMyself() {
    var array = this.snake_pos_Arr;
    for (let i = 1; i < array.length;) {
        if (array[i].x == array[0].x && array[i].y == array[0].y) {
            console.log("有重复的");
            break;
        } else {
            if (i == array.length - 1) {
                console.log("没有重复的---");
                break;
            } else {
                i++;
            }
        }
    }
 },

同时,蛇会在碰到周围墙壁的时候死亡,也就是判断蛇头坐标是否出界:

//判断是否撞墙死亡
judgeSnakeDie() {
    //第一种情况,出界死亡
    if (this.snake_pos_Arr[0].x > 16 || this.snake_pos_Arr[0].x < -16 || this.snake_pos_Arr[0].y > 9 || this.snake_pos_Arr[0].y < -9) {
        console.log("出界死亡");
    }
},

7、吃到食物

简单来说就是蛇头和食物的碰撞检测,这里没有用刚体,用的是坐标直接检测:

/**
 * 判断头和食物是否碰撞
 * @param {*} pos_A 
 * @param {*} pos_B 
 */
collisionDetection(pos_A, pos_B, direction) {
    // console.log("判断头和食物是否碰撞 == ", pos_A, pos_B);
    var _dx = pos_B.x - pos_A.x;
    var _dy = pos_B.y - pos_A.y;
    if ((direction == 1 && _dx == 0 && _dy == 1) || (direction == 2 && _dx == 0 && _dy == -1) || (direction == 3 && _dx == -1 && _dy == 0) || (direction == 4 && _dx == 1 && _dy == 0)) {
        console.log("和食物碰撞到了");    
        return true;
    } else {
        return false;
    }
},

这里没有用勾股定理或者直接判断横向坐标或纵向坐标小于1,因为没用action,而是蛇身上的节点直接运动到下一坐标的像素位置,所以用勾股定理或者坐标判断,会导致斜向的时候也会检测到已碰撞(如食物坐标为(1,1),蛇头坐标为(2,2)或者(0,0),都会判断已碰撞到),所以只好在判断蛇运动方向的基础上,进行坐标判断。

既然撞到了食物,自然需要后续处理,即蛇身增长,食物随机到新位置:

//碰撞之后的处理
afterCrash() {
	//创建个新的的一节
    var newSnakeSprite = this.createSnakeSprite(); 
    //添加到蛇节点数组的首位
    this.snake_Arr.unshift(newSnakeSprite);
    //将食物的坐标添加到蛇身坐标的首位
    this.snake_pos_Arr.unshift(this.FoodPos);
    //创建食物新坐标
    var pos = this.createFoodPos();
    //设置新食物的像素位置
    this.Food.setPosition(pos.x * this.gridWidthAndHeight, pos.y * this.gridWidthAndHeight);
},

8、优化处理

1)、分数添加

可以给加个分数,吃一个食物加一分。

2)、死亡之后的处理

死亡之后,可以重新开始,开始之前需要界面清理,比如数组清空,节点数组里面的节点销毁等。

3)、点击方向和蛇自身运动的优化

因为蛇在没有改变方向的情况下是匀速自动运动,如果点击,则短时间内蛇会运动两下,所以需要在改变方向的时候,进行定时器的重置。

4)、逻辑优化

蛇每运行一步,都需要进行死亡检测和是否吃到食物的检测。

9、效果图如下:

js之最原始版贪吃蛇的制作_第3张图片
如有建议,请多指教。

你可能感兴趣的:(js之最原始版贪吃蛇的制作)