Unity3D 小案例 像素贪吃蛇 第三期 蛇的碰撞(完结)
像素贪吃蛇
碰撞蛇身
当蛇头碰撞到蛇身时,游戏应该判定为失败。
找到蛇身预制体,添加 Body 标签和碰撞体,碰撞体的大小为 0.5,跟蛇头和蛇身的碰撞体范围一样,避免因碰撞范围过大而产生错误的碰撞效果。

修改一下 OnTriggerEnter2D 方法,判断碰撞到 Body 标签的物体,判定游戏失败,蛇停止移动。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| using System.Collections; using System.Collections.Generic; using UnityEngine;
public class Snake : MonoBehaviour { void OnTriggerEnter2D(Collider2D other) { if (other.CompareTag("Food")) { } else if (other.CompareTag("Body")) { CancelInvoke(nameof(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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| using System.Collections; using System.Collections.Generic; using UnityEngine;
public class Snake : MonoBehaviour { Vector2 initPos;
void Start() { initPos = transform.position;
for (int i = 0; i < initBodyCount; i++) { GenerateBody(true); }
} void GenerateBody(bool isInit = false) { GameObject obj = Instantiate(body);
if (isInit) { obj.transform.position = initPos + Vector2.left; initPos = obj.transform.position; } else { if (bodyList.Count > 0) { GameObject lastBody = bodyList[bodyList.Count - 1]; obj.transform.position = lastBody.transform.position; } else { obj.transform.position = transform.position; } }
} }
|
现在运行游戏,蛇身的初始位置就不会跟蛇头重叠了。

另外,蛇头的层级也要调整一下,当蛇头与蛇身重叠时,让蛇头能够显示在蛇身之上。

运行效果:

添加墙壁
创建四个正方形,调整缩放和位置,调整颜色,分别布置在上下左右四个方向。
因为蛇的移动范围,在 X 轴是 [-8, 8]
,在 Y 轴是 [-4, 4]
,所以左右的墙壁分别放置在 X 轴的 -9 和 9 的位置,上下的墙壁分别放置在 Y 轴的 5 和 -5 的位置。

然后给墙壁也添加标签 Wall,并且要添加碰撞体,碰撞体的大小保持默认的 1 即可。

在代码中也添加碰撞墙壁的逻辑。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| using System.Collections; using System.Collections.Generic; using UnityEngine;
public class Snake : MonoBehaviour { void OnTriggerEnter2D(Collider2D other) { if (other.CompareTag("Food")) { } else if (other.CompareTag("Body") || other.CompareTag("Wall")) { CancelInvoke(nameof(Move)); } } }
|
运行效果:

游戏失败
显示失败界面
简单搭建一个游戏失败界面。

创建一个 UIGameOver.cs
脚本,引用命名空间 UnityEngine.UI
,定义 UI 组件变量,拖拽引用。
1 2 3 4 5 6 7 8 9 10 11 12 13
| using UnityEngine; using UnityEngine.UI;
public class UIGameOver : MonoBehaviour { public Button btnRestart; public CanvasGroup canvasGroup;
void Start() {
} }
|

创建一个 GameManager.cs
脚本,引用命名空间 System
,创建单例。
定义一个 Action
事件 showGameOver
,带一个布尔值参数,并对外提供一个接口,用于触发事件。
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; using UnityEngine;
public class GameManager : MonoBehaviour { public static GameManager instance; public event Action<bool> showGameOver;
void Awake() { if (instance == null) { instance = this; } else { Destroy(gameObject); } }
public void TriggerGameOver(bool isShow) { showGameOver.Invoke(isShow); } }
|
界面脚本添加事件监听,根据是否显示界面,修改 canvasGroup
的参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| using UnityEngine; using UnityEngine.UI;
public class UIGameOver : MonoBehaviour { public Button btnRestart; public CanvasGroup canvasGroup;
void Start() { GameManager.instance.showGameOver += (isShow) => { canvasGroup.alpha = isShow ? 1 : 0; canvasGroup.blocksRaycasts = isShow; }; } }
|
当蛇碰撞时,触发事件显示游戏失败界面。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| using System.Collections; using System.Collections.Generic; using UnityEngine;
public class Snake : MonoBehaviour { void OnTriggerEnter2D(Collider2D other) { if (other.CompareTag("Food")) { } else if (other.CompareTag("Body") || other.CompareTag("Wall")) { CancelInvoke(nameof(Move)); GameManager.instance.TriggerGameOver(true); } } }
|
运行效果:

重开游戏
在 UIGameOver.cs
脚本上添加按钮事件,隐藏失败界面,重新开始游戏。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| using UnityEngine; using UnityEngine.UI;
public class UIGameOver : MonoBehaviour { public Button btnRestart; public CanvasGroup canvasGroup;
void Start() {
btnRestart.onClick.AddListener(()=> { GameManager.instance.TriggerGameOver(false); GameManager.instance.RestartGame(); }); } }
|
在 GameManager.cs
脚本上添加重开游戏的事件和触发接口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| using System; using UnityEngine;
public class GameManager : MonoBehaviour {
public event Action restartGame;
public void RestartGame() { restartGame.Invoke(); } }
|
在 FoodManager.cs
脚本上添加重置网格接口,同时定义一个变量记录当前生成的食物,在重置网格时销毁当前食物。
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 FoodManager : MonoBehaviour { public GameObject currentFood; public void GenerateFood() {
currentFood = obj; } public void ResetGrid() { gridList.Clear(); for (int i = 0; i < rowMax; i++) { for (int j = 0; j < colMax; j++) { gridList.Add(new Vector3(borderLeft + j, borderTop - i, 0)); } } Destroy(currentFood); } }
|
最后,在 Snake.cs
脚本上添加事件监听,重置网格、蛇头、蛇身、食物,最后再次开启定时器,让蛇头重新开始移动。
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
| using System.Collections; using System.Collections.Generic; using UnityEngine;
public class Snake : MonoBehaviour { void Start() { GameManager.instance.restartGame += ()=> { FoodManager.instance.ResetGrid();
for (int i = 0; i < bodyList.Count; i++) { Destroy(bodyList[i]); } bodyList.Clear();
transform.position = Vector3.zero; FoodManager.instance.MarkGridList(transform.position, true); direction = Vector2.right; lastDirection = Vector2.right; firstDirection = Vector2.right;
initPos = transform.position;
for (int i = 0; i < initBodyCount; i++) { GenerateBody(true); }
FoodManager.instance.GenerateFood();
InvokeRepeating(nameof(Move), time, time); }; } }
|
运行效果:
