贪吃蛇

思考并回答以下问题:

  • 请阐述贪吃蛇开发思路。
  • 如何拓展贪吃蛇的游戏玩法?
  • 蛇的移动有哪两种方案,哪一种更好?

游戏规则

1.游戏地图是一个长方形场地,可以看作是一个由30×20的方格组成的地图,食物和组成蛇身的块占一个小方格。
2.玩家操控一条贪吃的蛇在长方形的场地里行走,贪吃蛇会按玩家所按的方向行走或者转弯。
3.蛇头吃到豆子后,蛇身会变长,并得分。
4.贪吃蛇碰到墙壁或者自身,游戏结束或者损失一条生命。

程序思路

地图的生成

将游戏画面看成是30×20的二维数组。食物和组成蛇的部分占一个单位。

食物出现

1.采用随机数生成食物出现的坐标。
2.判断食物出现的坐标是否是在贪吃蛇身体范围内。将食物的坐标与贪吃蛇身体的每一个元素的坐标进行比较,若有重合,则重新生成随机数,直到不在贪吃蛇的身体范围内为止。

蛇的数据结构

用列表存储蛇所有的块,吃到食物之后,就在列表中添加一个元素。

贪吃蛇移动算法

  • [1] 移动方向:通过用户输入,获得贪吃蛇的移动方向。

  • [2] 移动:如果用坐标的自加或者自减运算来做蛇的移动,代码计算量会很大。我们可以用一种简单的方法实现贪吃蛇的移动,当没有吃到食物时,用一个变量存储蛇头移动前的位置,然后在蛇头移动前的位置添加一个元素,再移除尾部最后一个元素,实现贪吃蛇的移动。

蛇的增长

当吃到食物时,用一个变量存储蛇头移动前的位置,在蛇头移动前的位置添加新的块,这样就实现了贪吃蛇的变长。如图1和图2所示。

图1 蛇的移动(没有吃到食物)

图2 蛇的移动(吃到食物)

判断蛇头是否撞到了自身

遍历组成蛇的所有的块,判断非蛇头的位置是否与蛇头的位置相同,如果有相同,则蛇碰到了自身,若没有相同,则没有碰到自身。

边界判断

将蛇头的坐标与边界的坐标进行比较,若蛇头的坐标与边界有重合的地方,则蛇头碰到了边界,游戏结束。

游戏流程图

游戏程序实现

点击下载贪吃蛇资源

生成食物

  • [1] 新建一个空物体,重命名为SpawnFood。
  • [2] 新建一个名为SpawnFood的脚本,此脚本用于生成食物。将脚本赋给SpawnFood物体。
  • [3] 运行游戏。你会看到场景中产生了一个小方块,这将会是贪吃蛇的食物。试着多运行几次游戏,你会发现每次食物产生的位置都是不同的,并且食物产生的位置都是位于边界内,
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
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SpawnFood : MonoBehaviour
{
private GameObject foodPrefab; //食物预制体

private Transform borderLeft; //左边界
private Transform borderRight; //右边界
private Transform borderTop; //上边界
private Transform borderBottom;//下边界

// Use this for initialization
void Start()
{
//调用生成食物的函数
Spawn();
}

public void Spawn()
{
//在上下边界范围内生成随机数
int y = Random.Range((int)borderBottom.position.y + 1, (int)borderTop.position.y - 1);
//在左右边界范围内生成随机数
int x = Random.Range((int)borderLeft.position.x + 1, (int)borderRight.position.x - 1);
//实例化食物
Instantiate(foodPrefab, new Vector2(x, y), Quaternion.identity);
}
}

蛇的移动

[1] 放置“蛇头”。将Prefab文件夹中的HeadPrefab预制体添加到场景中,重命名为Head,调整其位置到边界框的中间,场景中蓝色的小方块就是贪吃蛇最初始的模样。

贪吃蛇放置好之后,我们需要给贪吃蛇添加脚本,让它动起来。

[2] 新建一个名为Snake的脚本,用于控制贪吃蛇的移动。
[3] 移动蛇。在Snake脚本中添加一个名为Move的函数,实现蛇的移动。
[4] 将Snake脚本赋给场景中的Head物体。

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class Snake : MonoBehaviour
{
Vector2 dir = Vector2.right;//蛇的移动方向
bool ate = false; //是否吃到食物

public GameObject tailPrefab; //表示蛇身体的预制体

List<Transform> tail = new List<Transform>();//存放蛇身和蛇头的列表

int score; //分数
public Text scoreText; //显示分数的UI
public Text gameOverText; //游戏结束的UI

private Vector2 lastDir;

float lastTime = 0.0f;

int flag = 1;//标记方向,1,向右,2,向左,3,向上,4,向下

// Use this for initialization
void Start()
{
//循环调用Move函数,是贪吃蛇能自动移动
InvokeRepeating("Move", 0.3f, 0.3f);
}

// Update is called once per frame
void Update()
{
//向下并且上一次不向上
if (Input.GetKeyDown(KeyCode.DownArrow)&&flag!=3)
{
dir = Vector2.down;
flag = 4;
}
//向上并且上一次不向下
if (Input.GetKeyDown(KeyCode.UpArrow)&&flag!=4)
{
dir = -Vector2.down;
flag = 3;
}
//向左并且上一次方向不向右
if (Input.GetKeyDown(KeyCode.LeftArrow)&&flag!=1)
{
dir = -Vector2.right;
flag = 2;
}
//向右并且上一次方向不向左
if (Input.GetKeyDown(KeyCode.RightArrow)&&flag!=2)
{
dir = Vector2.right;
flag = 1;
}

}

void Move()
{

// lastDir = dir;
Vector2 v = transform.position;

//移动
transform.Translate(dir);


//碰到食物,在蛇头移动前的位置插入一个元素
if (ate)
{
GameObject g = (GameObject)Instantiate(tailPrefab, v, Quaternion.identity);

tail.Insert(0, g.transform);

ate = false;
}
//没有碰到食物的移动,移动蛇头,在蛇头移动前的位置插入一个元素,删除最后一个元素
else if (tail.Count > 0)
{
tail[tail.Count - 1].position = v;

tail.Insert(0, tail[tail.Count - 1]);

tail.RemoveAt(tail.Count - 1);

}

lastTime = Time.time;
lastDir = dir;
}

void OnTriggerEnter2D(Collider2D collision)
{
//碰到食物
if (collision.name.StartsWith("Done_FoodPrefab"))
{
ate = true;

//分数+1
score++;
//显示分数
scoreText.text = "分数:" + score;

//生成下一个食物
FindObjectOfType<Done_SpawnFood>().Spawn();
//销毁当前食物
Destroy(collision.gameObject);
}
//碰到边界
if (collision.name.StartsWith("border"))
{
Debug.Log("撞到边界,游戏结束!");
gameOverText.text = "Game Over!";
//停止游戏
dir = Vector2.zero;
Time.timeScale = 0;
}
//碰到自身
if (collision.name.StartsWith("Done_TailPrefab"))
{
Debug.Log("撞到自身,游戏结束!");
gameOverText.text = "Game Over!";
//停止游戏
Time.timeScale = 0;
}

}
}
0%