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

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.mzph.cn/news/879816.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【VitualBox】VitualBox的网络模式+网络配置

VirtualBox 1. 简介 VirtualBox 是一款开源虚拟机软件&#xff0c;使用者可以在VirtualBox上安装并且执行Solaris、Windows、DOS、Linux、OS/2 Warp、BSD等系统作为客户端操作系统。 2. 六种网络接入模式 VirtualBox提供了多种网络接入模式&#xff0c;他们各有优缺点&#xf…

YOLOv8改进 | 自定义数据集训练 | AirNet助力YOLOv8检测

目录 一、本文介绍 二、AirNet原理介绍 2.1 对比基降解编码器&#xff08;CBDE&#xff09; 2.2 降解引导修复网络&#xff08;DGRN&#xff09; 三、yolov8与AirNet结合修改教程 3.1 核心代码文件的创建与添加 3.1.1 AirNet.py文件添加 3.1.2 __init__.py文件添加 3…

【qt】一个WPS项目了解qt界面设计的基本套路

项目功能演示: 放心食用!最后有完整代码. 超级详细,期待您的一个点赞❥(^_-) 一览全局: WPS项目目录 一.创建项目二.导入资源三.ui设计四.字号选择框初始化五.滚动条初始化六.添加自定义文本类七.初始化action状态八.新建文档九.打开文件十.保存与另存为十一.打印/打印预览十…

vue 入门一

参考&#xff1a;丁丁的哔哩哔哩 1.使用vue 1.1 使用CDN的方式使用Vue mount和<div id"counter">关联起来 1.2 vue中的createApp import { createApp } from "vue"; import App from "./App.vue"; createApp(App).mount("#app&qu…

PyTorch使用------自动微分模块

目录 &#x1f354; 梯度基本计算 1.1 单标量梯度的计算 1.2 单向量梯度的计算 1.3 多标量梯度计算 1.4 多向量梯度计算 1.5 运行结果&#x1f4af; &#x1f354; 控制梯度计算 2.1 控制不计算梯度 2.2 注意: 累计梯度 2.3 梯度下降优化最优解 2.4 运行结果&#x1…

数字工厂管理系统与MES系统在实际应用中有哪些区别

随着制造业的数字化转型步伐加快&#xff0c;数字工厂管理系统与制造执行MES系统作为两大关键工具&#xff0c;在实际应用中展现出了明显的差异。本文将从实际应用的角度&#xff0c;详细探讨这两种系统之间的主要区别。 数字工厂管理系统的实际应用 数字工厂管理系统侧重于对…

掌握Spring Boot数据库集成:用JPA和Hibernate构建高效数据交互与版本控制

在现代应用开发中&#xff0c;数据库操作是核心环节之一。Spring Boot提供了简化数据库集成的强大工具&#xff0c;而JPA&#xff08;Java Persistence API&#xff09;和Hibernate是两种非常流行的ORM&#xff08;对象关系映射&#xff09;框架&#xff0c;可以帮助我们将对象…

如何删除EXCELL文件中的空行?

1&#xff0c;选择某一列 2&#xff0c;点击《开始》《查找和选择》>《定位条件》&#xff0c;调出《定位条件》的选择框&#xff1b; 3&#xff0c;在定位条件选项框&#xff0c;选择《空值》&#xff1b; 4&#xff0c;找到变灰被选中的某一行&#xff0c;右击《删除》 5&…

GitLab权限及设置

之前很少关注这些&#xff0c;项目的权限&#xff0c;一般由专门的管理人员设置。 但自己创建的项目自己可以设置权限。下面是一些笔记。 GitLab中用户权限_gitlab 权限-CSDN博客 开发中遇到要将自己这块的代码上传到Git&#xff0c;由其他组的同事拉取后继续开发。上传代码后…

JavaScript 笔记汇总

JavaScript 笔记汇总 引入方式 内部方式 通过 script 标签包裹 JavaScript 代码。 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>JavaScript 基础 - 引入方式</title> </head> <…

SpringBoot Kafka发送消息与接收消息实例

前言 Kafka的基本工作原理 我们将消息的发布&#xff08;publish&#xff09;称作 producer(生产者)&#xff0c;将消息的订阅&#xff08;subscribe&#xff09;表述为 consumer&#xff08;消费者&#xff09;&#xff0c;将中间的存储阵列称作 broker(代理)&#xff0c;这…

QT打开摄像头采集

QT打开摄像头采集 今天好不容易把opencv的环境装好&#xff0c;然后想学习一下人脸识别的功能&#xff0c;但是在图书馆坐了4个多小时了&#xff0c;屁股疼就先写个摄像头采集的功能&#xff0c;明天继续学习吧&#xff0c;废话不多&#xff0c;嚼个奶片开始发车&#xff01;&…

JVM java主流的追踪式垃圾收集器

目录 前言 分代垃圾收集理论 标记清除算法 标记复制算法 标记整理法 前言 从对象消亡的角度出发, 垃圾回收器可以分为引用计数式垃圾收集和追踪式垃圾收集两大类, 但是java主流的一般是追踪式的垃圾收集器, 因此我们重点讲解. 分代垃圾收集理论 分代收集这种理…

Linux Vim编辑器常用命令

目录 一、命令模式快捷键 二、编辑/输入模式快捷键 三、编辑模式切换到命令模式 四、搜索命令 注&#xff1a;本章内容全部基于Centos7进行操作&#xff0c;查阅本章节内容前请确保您当前所在的Linux系统版本&#xff0c;且具有足够的权限执行操作。 一、命令模式快捷键 二…

企业专用智能云盘 | 帮助企业便捷管控企业文档 | 天锐绿盘云文档安全管理系统

由于当前多数企业内部的办公文件普遍散落于各员工电脑中&#xff0c;导致存在诸多潜在的文档使用风险。为优化团队协作效率&#xff0c;天 锐 绿盘是一款集文档统一管理、高效协同于一体的企业云盘&#xff0c;帮助企业解决文档管理中的诸多难题。 【地址&#xff1a;点击了解天…

【2023工业异常检测文献】SimpleNet

SimpleNet:ASimpleNetworkforImageAnomalyDetectionandLocalization 1、Background 图像异常检测和定位主要任务是识别并定位图像中异常区域。 工业异常检测最大的难题在于异常样本少&#xff0c;一般采用无监督方法&#xff0c;在训练过程中只使用正常样本。 解决工业异常检…

TCP客户端编码和解码处理:发送和接收指定编码消息

文章目录 引言基于Netty实现TCP客户端Netty发送GBK编码指令Netty接收GBK编码基于Channel发送指令基于ChannelHandlerContext发送指令:建立连接时发送登陆指令开启日志,查看报文信息基于ChannelInboundHandlerAdapter进行业务逻辑处理原生API实现TCP客户端基于DataOutputStrea…

AI预测福彩3D采取888=3策略+和值012路或胆码测试9月19日新模型预测第92弹

经过90多期的测试&#xff0c;当然有很多彩友也一直在观察我每天发的预测结果&#xff0c;得到了一个非常有价值的信息&#xff0c;那就是9码定位的命中率非常高&#xff0c;90多期一共只错了10次&#xff0c;这给喜欢打私房菜的朋友提供了极高价值的预测结果~当然了&#xff0…

C语言中的assert断言

Assert断言 断言是程序中处理异常的一种高级形式。可以在任何时候启用和禁用断言验证&#xff0c;因此可以在测试时启用断言&#xff0c;而在部署时禁用断言。同样&#xff0c;程序投入运行后&#xff0c;最终用户在遇到问题时可以重新启用断言。 用法&#xff1a; #…

23.面试题02.07链表相交

public class Solution {public ListNode getIntersectionNode(ListNode headA, ListNode headB) {ListNode apheadA;ListNode bpheadB;int lenA0,lenB0;//求两个链表长度while(ap!null){apap.next;lenA;}while(bp!null){bpbp.next;lenB;}apheadA;bpheadB;int len0;//用来计算让…