先来了解一些简单的基础知识
下面是一个20 x 20的矩阵 用来表示贪吃蛇的地图
坐标原点为左下角
我们需要有一个数组来保存当前蛇的蛇身体
snake数组是存在顺序的,数组的第一个元素是蛇头的位置,数组的最后一位就是蛇的尾巴
1
| let snake: Array<number> = [202, 201];
|
接下来是蛇的移动,也就是数组数据的移动
- 上边移动 蛇头数据+20
- 下边移动 蛇头数据-20
- 左边移动 蛇头数据-1
- 右边移动 蛇头数据+1
上边只说到蛇头数据,因为我们只需要修改一个蛇头的数据,不用修改整条蛇的数据
蛇的数据
当前蛇的方向是右边,也就是数据+1,加入一个新的蛇头
1
| snake = [203, 202, 201];
|
当前蛇的长度变成了3,可是我们的蛇应该只有2的长度,还需要把蛇的尾巴去掉一个
这样蛇就完成了向右一格的移动 上下左移动都是同理,这里就不赘述了
下面进入正题了
10分钟编写一个贪吃蛇,先确定下规则
- 贪吃蛇由上下左右键位控制
- 蛇头能穿越墙壁从另一面出来
- 吃到食物增加一个长度
- 撞到自己的身体即为失败
新建一个空的项目
加入一个空节点,用来承载地图,命名为map
接下来是编写脚本Map.ts
初始化一些变量
1 2 3 4 5 6
| snake: Array<number> = [202, 201]; food = 203; moveOffset = 1; nextIndex = 0; g: cc.Graphics = null; canControl = false;
|
先加入控制的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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; }
|
简单分析这段代码:
1
| 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 获得元素在数组中的位置(这里用来判断是否在数组中)
- 循环遍历食物不存在与当前的蛇体内 不会无限循环 因为覆盖会在上面的函数中跳出
- 尾部去除的格子用背景色覆盖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| 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方法
1 2 3 4
| draw(mapIndex: number, color: cc.Color) { this.g.fillColor = color; this.g.fillRect(mapIndex % 20 * 20 - 2 , ~~(mapIndex / 20) * 20 - 2, 16, 16) }
|
ps 可以扩展的部分
- 以上就是主要代码的部分,下面是一些扩展和方向,就不属于10分钟内的了
- 设置蛇头颜色,清理尾部格子的时候顺便清理snake[1]为蛇身颜色即可
- 多个食物,设置food为数组,判断数组内是否是蛇头就好了
- 关卡加速,控制帧率越来越高即可 ps: 30差不多就难以操作了
源码地址: https://gitee.com/limo/snake