Unity3D 小案例 像素贪吃蛇 02 蛇的觅食

embedded/2024/9/22 23:02:48/

Unity3D 小案例 像素贪吃蛇 第二期 蛇的觅食

像素贪吃蛇

食物生成

在场景中创建一个 2D 正方形,调整颜色,添加 Tag 并修改为 Food。

创建食物

然后拖拽到 Assets 文件夹中变成预制体。

预制体

创建食物管理器 FoodManager.cs,添加单例,可以设置食物生成的坐标范围,提供生成一个食物的方法。

因为 Random.Range 的取值范围是 [min, max),为了取到 max 的值,需要给右边界加一。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class FoodManager : MonoBehaviour
{public static FoodManager instance;public GameObject food;public int borderLeft = -8;public int borderRight = 8;public int borderTop = 4;public int borderBottom = -4;void Awake(){if (instance == null){instance = this;}else{Destroy(gameObject);}}void Start(){// 初始生成一个食物GenerateFood();}/// <summary>/// 生成食物/// </summary>public void GenerateFood(){GameObject obj = Instantiate(food, transform);int x = Random.Range(borderLeft, borderRight + 1);int y = Random.Range(borderBottom, borderTop + 1);obj.transform.position = new Vector3(x, y, 0);}
}

在场景中创建节点,挂上脚本,拖拽引用。

食物管理器

运行游戏,可以看到场景中生成了一个食物。

生成一个食物

吃掉食物

给食物的预制体添加碰撞体,勾选 Is Trigger

添加碰撞体

同样,蛇头也要添加碰撞体,还要再添加一个刚体,Body Type 设置为 Kinematic,不需要受到重力影响。

添加刚体

Snake.cs 中添加碰撞函数,判断碰撞物体的标签是 Food,就销毁食物,生成新的蛇身,并生成下一个食物。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class Snake : MonoBehaviour
{// ...void OnTriggerEnter2D(Collider2D other){if (other.CompareTag("Food")){Destroy(other.gameObject);GenerateBody();FoodManager.instance.GenerateFood();}}
}

此时运行游戏,蛇头可以吃掉食物了。

但是有时候蛇头还未到达食物的位置,食物就被吃掉了,甚至蛇头只是经过食物的附近,食物也消失了。这是因为碰撞体的范围问题,默认的 Size 是 (1, 1),可以稍微调小一些,例如 (0.5, 0.5)

调整碰撞体大小

调整后的效果:

吃掉食物

食物位置

目前场景范围适中,生成的食物都在空地,但是当蛇越来越长的时候,会发现食物生成的位置有可能在蛇的身上。

我们应该让食物始终都在空地生成。

那么,对于一个坐标是否为空地,就需要做一些标记。

网格

目前食物生成的坐标取值范围,在 X 轴是 [-8, 8],在 Y 轴是 [-4, 4]

如果把这些坐标点看成是一个网格,可以按照行列来看。

左上角是 (-8, 4),是第 0 行,第 0 列,索引为 0。

右上角是 (8, 4),是第 0 行,第 16 列,索引为 16。

左下角是 (-8, -4),是第 8 行,第 0 列,索引为 136。

右下角是 (8, -4),是第 8 行,第 16 列,索引为 152。

注意:这里的索引是从第 0 行开始,从左到右递增。行数增加时,索引继续计数。

网格

网格列表

FoodManager.cs 中,添加一个 Vector3 列表,X 和 Y 记录坐标,Z 记录是否空地(0 表示空地,1 表示有物体占用)。

这里总行数是上边界减去下边界,还要加上一个端点,总共 9 行。

总列数是右边界减去左边界,还要加上一个端点,总共 17 列。

根据行列数,依次添加 Vector3 到列表中,Z 默认是 0。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class FoodManager : MonoBehaviour
{// ...public List<Vector3> gridList;public int rowMax = 0;public int colMax = 0;void Start(){rowMax = borderTop - borderBottom + 1;colMax = borderRight - borderLeft + 1;for (int i = 0; i < rowMax; i++){for (int j = 0; j < colMax; j++){gridList.Add(new Vector3(borderLeft + j, borderTop - i, 0));}}}
}

然后提供一个标记网格列表的方法,把传入的坐标转成 int,判断边界,换算行列,计算索引,根据索引从网格列表中取出一个网格点,更新标记。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class FoodManager : MonoBehaviour
{// .../// <summary>/// 标记网格列表/// </summary>/// <param name="pos">坐标位置</param>/// <param name="flag">标记</param>public void MarkGridList(Vector3 pos, bool flag){int x = (int)pos.x;int y = (int)pos.y;// 坐标超出边界if (x < borderLeft || x > borderRight) return;if (y < borderBottom || y > borderTop) return;// 换算行列int row = borderTop - y;int col = x - borderLeft;// 计算索引int index = col + row * colMax;// 索引超出边界if (index < 0 || index > gridList.Count - 1) return;// 取出网格点,标记是否空地Vector3 grid = gridList[index];grid.z = flag ? 1 : 0;// 更新网格点gridList[index] = grid;}
}

标记网格

在游戏开始时,蛇头会占用一个网格,生成的身体也需要标记网格。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class Snake : MonoBehaviour
{void Start(){// 初始生成身体for (int i = 0; i < initBodyCount; i++){GenerateBody();}FoodManager.instance.MarkGridList(transform.position, true);// ...}void GenerateBody(){GameObject obj = Instantiate(body);// ...FoodManager.instance.MarkGridList(obj.transform.position, true);}
}

在蛇的移动过程中,也要动态地标记网格。

蛇头和身体移动后都要标记网格已经被占用,只有在最后一个身体移动前,标记当前网格位置为空地。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class Snake : MonoBehaviour
{void Move(){// ...// 移动前,先标记旧的位置posMarkFirst = transform.position;transform.Translate(direction);// 标记蛇头移动后的网格位置FoodManager.instance.MarkGridList(transform.position, true);// ...for (int i = 0; i < bodyList.Count; i++){// 最后一个身体移动前,标记当前网格位置为空地if (i == bodyList.Count - 1){FoodManager.instance.MarkGridList(bodyList[i].transform.position, false);}// ...// 每个身体移动后,标记当前网格位置FoodManager.instance.MarkGridList(bodyList[i].transform.position, true);}}
}

食物也会占用网格,每次生成食物时,也要标记网格。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class FoodManager : MonoBehaviour
{public void GenerateFood(){GameObject obj = Instantiate(food, transform);int x = Random.Range(borderLeft, borderRight + 1);int y = Random.Range(borderBottom, borderTop + 1);obj.transform.position = new Vector3(x, y, 0);// 标记食物占用的网格位置MarkGridList(obj.transform.position, true);}
}

筛选空地

在食物生成时,不能单纯用随机数来确定坐标位置,而是要从网格列表中,筛选未被占用的网格点,然后从这些网格点中随机取出一个位置。

定义一个 filterList,用来存储筛选后的网格点。

每次生成食物时,需要先清理 filterList,然后从网格列表中,筛选 Z 为 0(表示未被占用)的网格点,添加到筛选列表中。

然后再生成随机数,从筛选列表中取出网格点,赋值位置给生成的食物。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class FoodManager : MonoBehaviour
{// ...public List<Vector3> filterList;// ...public void GenerateFood(){// 清理筛选列表filterList.Clear();for (int i = 0; i < gridList.Count; i++){// 筛选未被占用的网格点if (gridList[i].z == 0){filterList.Add(gridList[i]);}}// 没有空地了if (filterList.Count == 0) return;// 随机取出一个空地int index = Random.Range(0, filterList.Count);Vector3 pos = filterList[index];GameObject obj = Instantiate(food, transform);// int x = Random.Range(borderLeft, borderRight + 1);// int y = Random.Range(borderBottom, borderTop + 1);// obj.transform.position = new Vector3(x, y, 0);obj.transform.position = pos;// 标记食物占用的网格位置MarkGridList(obj.transform.position, true);}
}

至此,当蛇身越来越长时,也不会出现食物生成在蛇身上的情况了。

运行效果:

食物位置


http://www.ppmy.cn/embedded/112838.html

相关文章

LineageOS源码下载和编译(Xiaomi Mi 6X,wayne)

版权归作者所有&#xff0c;如有转发&#xff0c;请注明文章出处&#xff1a;https://cyrus-studio.github.io/blog/ 源码下载 LineageOS官网&#xff1a;https://lineageos.org/ LineageOS源码 github 地址&#xff1a;https://github.com/LineageOS/android LineageOS源码…

opencv anaconda yolov5安装流程

目录 1. opencv-4.72. anaconda的配置2.1 配置环境变量2.2 配置anaconda源2.3 常用的命令 3. Yolov5环境的安装 1. opencv-4.7 下载opencv-4.7的安装包 https://opencv.org/releases/ 安装最新的cmake sudo apt-get update sudo apt-get upgrade sudo apt install cmake安装o…

DisplayManagerService启动及主屏添加-Android13

// 以下代码是模拟DisplayManagerService在Android 13中启动并添加主屏的过程。 // 注意&#xff1a;这只是一个简化的代码示例&#xff0c;实际的系统服务可能包含复杂的逻辑和错误处理。 import android.hardware.display.DisplayManagerGlobal; import android.view.Displ…

学习笔记 韩顺平 零基础30天学会Java(2024.9.15)

P557 泛型应用实例 P558 泛型使用细节1 P560 泛型使用细节2 P560 泛型课堂练习 代码见Exceise P561 自定义泛型类 对于第二点&#xff0c;因为不知道类型&#xff0c;所以不知道开辟多少空间&#xff0c;因此不能初始化 第三点&#xff0c;静态方法与类相关的&#xff0c;在类…

【我的 PWN 学习手札】Fastbin Double Free

前言 Fastbin的Double Free实际上还是利用其特性产生UAF的效果&#xff0c;使得可以进行Fastbin Attack 一、Double Free double free&#xff0c;顾名思义&#xff0c;free两次。对于fastbin这种单链表的组织结构&#xff0c;会形成这样一个效果&#xff1a; 如果我们mallo…

ChatGPT的底层逻辑

“一些未知的东西正在做我们不知道的事情。” —— 阿瑟爱丁顿 “为何不尝试制作一个模拟儿童思维的程序呢&#xff1f;” —— 艾伦图灵 “只要是人脑能提出的问题&#xff0c;它就能够得到解决。” —— 库尔特哥德尔 开始 传说中的扫地僧&#xff0c;在现实中极其罕见。 有…

自动驾驶自动泊车场景应用总结

自动泊车技术是当前智能驾驶技术的一个重要分支,其目标是通过车辆自身的感知、决策和控制系统,实现车辆在有限空间内的自主泊车操作。目前自动泊车可分为半自动泊车、全自动泊车、记忆泊车、自主代客泊车四种产品形态,其中, 根据搭载传感器和使用场景的不同,全自动泊车又可…

python+adb

#!/usr/bin/python env # -*- coding: utf-8 -*- import os import sys import subprocess from time import sleepimport logging logging.basicConfig(levellogging.DEBUG) class ScreenCapture():def get_screen_size(self):"""获取手机分辨率""&q…