Unity3D 小案例 像素贪吃蛇 第一期 蛇的移动
像素贪吃蛇
今天来简单制作一个小案例,经典的像素贪吃蛇。
准备
首先调整一下相机的设置,这里使用灰色的纯色背景,正交视图。

接着,创建一个正方形,保存为预制体,一个蛇头,一个蛇身。蛇身稍微调成灰色。

初始生成
创建脚本 Snake.cs
,挂到 Snake 预制体上。
这里先定义初始身体数量,身体预制体,身体列表。
在 Start 方法里,初始生成一定数量的身体。
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 41 42 43 44 45
| using System.Collections; using System.Collections.Generic; using UnityEngine;
public class Snake : MonoBehaviour { public int initBodyCount = 3;
public GameObject body;
List<GameObject> bodyList = new List<GameObject>();
void Start() { for (int i = 0; i < initBodyCount; i++) { GenerateBody(); } }
void GenerateBody() { GameObject obj = Instantiate(body);
if (bodyList.Count > 0) { GameObject lastBody = bodyList[bodyList.Count - 1]; obj.transform.position = lastBody.transform.position; } else { obj.transform.position = transform.position; }
bodyList.Add(obj); } }
|
拖拽预制体引用,然后覆盖到预制体。
场景里面的 SnakeBody 可以删掉。

运行游戏,会在蛇头的位置生成三个身体,此时身体都是重叠的。
蛇的移动
定义移动方向和移动速度,在 Start 方法中,调用 InvokeRepeating
定时器,根据速度计算出定时器的调用时间间隔,每次调用 Move
方法往当前方向移动。
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
| using System.Collections; using System.Collections.Generic; using UnityEngine;
public class Snake : MonoBehaviour {
public Vector2 direction = Vector2.right; public float speed = 1;
void Start() {
float time = 1 / speed; InvokeRepeating(nameof(Move), time, time); }
void Move() { transform.Translate(direction); } }
|
此时只有蛇头会移动,身体是不动的。
要让身体跟着蛇头移动,需要在蛇头移动前,标记位置,再把旧的位置赋值给第一个身体。
第一个身体移动前,也要先标记位置,然后赋值给第二个身体。
所以要使用两个 Vector2
变量,循环标记。
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
| using System.Collections; using System.Collections.Generic; using UnityEngine;
public class Snake : MonoBehaviour {
Vector2 posMarkFirst; Vector2 posMarkLast;
void Move() { posMarkFirst = transform.position; transform.Translate(direction);
for (int i = 0; i < bodyList.Count; i++) { if (i % 2 == 0) { posMarkLast = bodyList[i].transform.position; bodyList[i].transform.position = posMarkFirst; } else { posMarkFirst = bodyList[i].transform.position; bodyList[i].transform.position = posMarkLast; } } } }
|
运行游戏,可以看到,蛇开始移动了。

控制方向
在 Update
方法中,获取键盘输入,改变蛇头的移动方向。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| using System.Collections; using System.Collections.Generic; using UnityEngine;
public class Snake : MonoBehaviour {
void Update() { float h = Input.GetAxisRaw("Horizontal"); float v = Input.GetAxisRaw("Vertical");
if(h != 0 || v != 0) { direction.x = h; direction.y = v; } } }
|
运行游戏,可以通过键盘方向键,改变蛇的移动方向。

但是问题来了,如上图所示,蛇头可以往反方向移动,还可以斜着移动,这些都是需要排除的情况。
简单分析一下,蛇头的移动方向,用二维向量来变量,有四种情况。
- 向上
(0, 1)
- 向下
(0, -1)
- 向左
(-1, 0)
- 向右
(1, 0)
当蛇头向右移动时,如果按键输入向左,那么 X 坐标相加等于 0。
同理,向上移动时,如果按键输入向下,那么 Y 坐标相加等于 0。
所以在改变方向时,判断当前方向和按键输入方向的相加情况,进行排除。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| using System.Collections; using System.Collections.Generic; using UnityEngine;
public class Snake : MonoBehaviour {
void Update() { float h = Input.GetAxisRaw("Horizontal"); float v = Input.GetAxisRaw("Vertical");
if(h != 0 || v != 0) { if (direction.x + h == 0 || direction.y + v == 0) return;
direction.x = h; direction.y = v; } } }
|
斜着移动时,也有四种情况。
- 左上
(-1, 1)
- 右上
(1, 1)
- 左下
(-1, -1)
- 右下
(1, -1)
它们的规律是,要么 X 坐标和 Y 坐标相等,要么 X 坐标和 Y 坐标相加等于 0。
所以在改变方向时,判断以上情况,进行排除。
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
| using System.Collections; using System.Collections.Generic; using UnityEngine;
public class Snake : MonoBehaviour {
void Update() { float h = Input.GetAxisRaw("Horizontal"); float v = Input.GetAxisRaw("Vertical");
if(h != 0 || v != 0) { if (direction.x + h == 0 || direction.y + v == 0) return; if (h == v || h + v == 0) return;
direction.x = h; direction.y = v; } } }
|
但是还没有完全解决问题,当蛇头向右移动时,如果快速按向上键,再按向左键,蛇头依然会往反方向移动,因为以上规则在二次按键时,可以顺利切换方向。
所以,每当定时器触发移动时,还需要再进行方向修正。
我们可以先记录第一次按键的方向,此时该方向是合理的。当触发移动时,如果当前方向和上次移动的方向正好相反,那么判定当前方向是进行了二次按键,把当前方向修正为第一次按键的合理方向。
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 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| using System.Collections; using System.Collections.Generic; using UnityEngine;
public class Snake : MonoBehaviour { public Vector2 lastDirection = Vector2.right; public Vector2 firstDirection = Vector2.right; public bool firstPress = true;
void Update() { float h = Input.GetAxisRaw("Horizontal"); float v = Input.GetAxisRaw("Vertical");
if(h != 0 || v != 0) { if (direction.x + h == 0 || direction.y + v == 0) return; if (h == v || h + v == 0) return;
direction.x = h; direction.y = v; if (firstPress) { firstPress = false; firstDirection.x = h; firstDirection.y = v; } } } void Move() { if (direction.x + lastDirection.x == 0 || direction.y + lastDirection.y == 0) { direction.x = firstDirection.x; direction.y = firstDirection.y; }
posMarkFirst = transform.position; transform.Translate(direction);
lastDirection = direction; firstPress = true; } }
|
最终的移动效果。
