先来了解一些简单的基础知识
下面是一个20 x 20的矩阵 用来表示贪吃蛇的地图
坐标原点为左下角
我们需要有一个数组来保存当前蛇的蛇身体
snake数组是存在顺序的,数组的第一个元素是蛇头的位置,数组的最后一位就是蛇的尾巴
let snake: Array<number> = [202, 201];
接下来是蛇的移动,也就是数组数据的移动
- 上边移动 蛇头数据+20
- 下边移动 蛇头数据-20
- 左边移动 蛇头数据-1
- 右边移动 蛇头数据+1
上边只说到蛇头数据,因为我们只需要修改一个蛇头的数据,不用修改整条蛇的数据
蛇的数据
snake = [202, 201];
当前蛇的方向是右边,也就是数据+1,加入一个新的蛇头
snake = [203, 202, 201];
当前蛇的长度变成了3,可是我们的蛇应该只有2的长度,还需要把蛇的尾巴去掉一个
snake = [203, 202];
这样蛇就完成了向右一格的移动 上下左移动都是同理,这里就不赘述了
下面进入正题了
10分钟编写一个贪吃蛇,先确定下规则
- 贪吃蛇由上下左右键位控制
- 蛇头能穿越墙壁从另一面出来
- 吃到食物增加一个长度
- 撞到自己的身体即为失败
新建一个空的项目
加入一个空节点,用来承载地图,命名为map
- map需要挂载一个画图的组件
接下来是编写脚本Map.ts
初始化一些变量
snake: Array<number> = [202, 201]; // 定义蛇的数组
food = 203; // 食物
moveOffset = 1; // 蛇头的变化值
nextIndex = 0; // 下一个蛇头的位置id
g: cc.Graphics = null;
canControl = false; // 是否可以控制
先加入控制的方法
onEnable() {
cc.systemEvent.on(cc.SystemEvent.EventType.KEY_DOWN, this.onKeyDown.bind(this));
}
onDisable() {
cc.systemEvent.off(cc.SystemEvent.EventType.KEY_DOWN, this.onKeyDown.bind(this));
}
onKeyDown(e: cc.Event.EventKeyboard) {
if (!this.canControl){
return;
}
this.moveOffset = this.snake[1] - this.snake[0] == (this.nextIndex = [-1, 20, 1, -20][e.keyCode - 37] || this.moveOffset) ? this.moveOffset : this.nextIndex;
}
简单分析这段代码:
this.moveOffset = this.snake[1] - this.snake[0] == (this.nextIndex = [-1, 20, 1, -20][e.keyCode - 37] || this.moveOffset) ? this.moveOffset : this.nextIndex;
- this.moveOffset 是蛇头的移动变量 也就是到下一格变化量
- this.snake[1] - this.snake[0] 获得当前蛇头不能变化的方法
- this.nextIndex 在这里做了下临时变量,记录操作值
- 左上右下的keyCode 分别为37 38 39 40 如果按其他按键就使用原来的移动变量
如果还不理解可以看下运算符的执行顺序
主要的移动和碰撞判断代码,有几个点需要简单分析下
- [].unshift 从头部加入数组
- [].indexOf 获得元素在数组中的位置(这里用来判断是否在数组中)
- 循环遍历食物不存在与当前的蛇体内 不会无限循环 因为覆盖会在上面的函数中跳出
- 尾部去除的格子用背景色覆盖
move() {
// 增加当前蛇头的位置
this.nextIndex = this.snake[0] + this.moveOffset
// 加入新蛇头
this.snake.unshift(this.nextIndex);
// 判断新蛇头有没撞到自己
if(this.snake.indexOf(this.nextIndex, 1) > 0) {
this.replay.getChildByName('result').getComponent(cc.Label).string = `score: ${this.snake.length * 100}`;
this.replay.active = true;
// 变红
this.snake.forEach(v => {
this.draw(v, cc.Color.RED);
});
return;
}
// 蛇头的越界修复
if (this.moveOffset == -20 && this.nextIndex < 0) {
this.snake[0] = this.snake[0] + 400;
} else if(this.moveOffset == 20 && this.nextIndex > 399) {
this.snake[0] = this.snake[0] - 400;
} else if (this.moveOffset == 1 && (this.nextIndex % 20 == 0 || this.nextIndex > 399)) {
this.snake[0] = this.snake[0] - 20;
} else if (this.moveOffset == -1 && (this.nextIndex % 20 == 19 || this.nextIndex < 0)) {
this.snake[0] = this.snake[0] + 20;
}
// 画出当前的蛇头
this.draw(this.snake[0], cc.Color.GREEN);
// 如果当前的蛇头是产生的糖果
// 产生新的糖果
if(this.nextIndex == this.food){
// 当前产生的块不存在蛇
while(this.snake.indexOf(this.food = ~~(Math.random() * 400)) >= 0);
// 产生新的事物
this.draw(this.food, cc.Color.YELLOW);
}else{
// 删除原来的点
this.draw(this.snake.pop(), cc.Color.GRAY);
}
setTimeout(this.move.bind(this), 1000/10)
};
最简单的Draw方法
- 画出当前方块的实体
draw(mapIndex: number, color: cc.Color) {
this.g.fillColor = color;
this.g.fillRect(mapIndex % 20 * 20 - 2 , ~~(mapIndex / 20) * 20 - 2, 16, 16)
}