虽然蛇看起来一长条,但是还是一个个单元连接起来的,所以我们先创建一个蛇单元,右击canvas—>创建节点—>创建渲染节点—>Sprite(单色),改变大小为40x40(因为Canvas的大小为1280x720,所以选了可以同时被整除的40),锚点改为(0,0)是为了方便计算。不过后来我将这个sprite添加到预制体了。
在脚本属性里面添加预制体和存储蛇的节点等各种需要的属性:
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
},
蛇在游戏开始时的方向一般默认为向右,这里创建长度为五节的向右的蛇,代码如下:
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)。
这样,一条五长度的蛇就创建完成,如下:
因为蛇的食物只有一块,所以在游戏开始的时候生成一次,之后如果被吃了随机换个位置就好,并且随机的位置不能和蛇本身重合。
代码如下:
/**
* 创建的食物
*/
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;
}
} },
蛇和食物都有了,接下来就是蛇的运动了,因为蛇是一节一节的,每一节对应一个坐标,而蛇运动的特点就是除了蛇头,蛇身上别的一节,都会走到前一节的位置上,所以代码原理就是在创建蛇的时候,将蛇身坐标存储到数组里,然后依据蛇的运动方向,在数组头部添加一个新的坐标,去除掉最后一个坐标,然后蛇的每个节点一一对应新数组的坐标位置,即可完成蛇身移动,代码如下:
假如蛇的运动方向为上(实际上需要四个方向判断),则:
//蛇头上移一个网格的像素
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;
}
}
蛇会在碰到自己身体的时候死亡,因为蛇全身的坐标被保存在一个数组里,如果蛇撞到了自身,则该数组会出现蛇头位置坐标和某个坐标重复,检测数组是否有和首位元素重复的元素即可判断蛇是否自杀:
//判断是不是碰撞到自己了
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("出界死亡");
}
},
简单来说就是蛇头和食物的碰撞检测,这里没有用刚体,用的是坐标直接检测:
/**
* 判断头和食物是否碰撞
* @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);
},
可以给加个分数,吃一个食物加一分。
死亡之后,可以重新开始,开始之前需要界面清理,比如数组清空,节点数组里面的节点销毁等。
因为蛇在没有改变方向的情况下是匀速自动运动,如果点击,则短时间内蛇会运动两下,所以需要在改变方向的时候,进行定时器的重置。
蛇每运行一步,都需要进行死亡检测和是否吃到食物的检测。