【Unity3D】3D物体摆放、场景优化案例Demo

目录

PlaceManager.cs(放置管理类)

Ground.cs(地板类) 和 GroundData.cs(地板数据类)

额外知识点说明

1、MeshFilter和MeshRenderer的Bounds区别

 2、Gizmos 绘制一个平行于斜面的立方体


通过网盘分享的文件:PlaceGameDemo2.unitypackage
链接: https://pan.baidu.com/s/1Jobzy8JaDqnBmRofNk2-Mw?pwd=fpfm 提取码: fpfm

PlaceManager.cs(放置管理类)

1、负责加载建筑数据表(BUILD_CONFIG_JSON_STR)json内容
        id:1,Build_A(立方体)、id:2,Build_B(球体)

2、构建世界World类对象(1000*1000*1000大小)以100*100*100的立方体为房间Room填充World空间。
        World由若干个Room组成,Room下可存放若干个处于Room范围内的物体(Unit封装)
        每个Unit均是动态创建并放入相关的Room对象内,Unit会持有1个所属Room列表(belongRoomList)。
        每个Unit创建时会存入<unitId, Unit>字典(unitDict)方便获取以及序列化使用

3、Update方法
        3.1 控制物体交互(点击物体开始拖拽、拖拽中、放下物体)
        3.2 可视处理,从<unitId, Unit>字典遍历所有已存在Unit,遍历Unit的belongRoomList房间是否位于摄像机视椎体内(是否可见),若可见则显示Unit持有的物体,否则隐藏。(可优化)
        3.3 按下键盘空格键,动态创建建筑配表的A或B物体并存放于默认地板defaultGround(应动态获取地板)(可优化 仅创建Unit对象放入World相应的Room对象内,由【可视处理】动态创建或显示物体)
        3.4 按下键盘D键,清空场景物体,加载场景序列化文件,反序列化构建World对象(globalWorld),之后会由【可视处理】动态创建或显示物体。
        3.5 按下键盘S键,序列化World对象(globalWorld)保存为(world_json.json)序列化<unitId, Unit>字典(unitDict)保存为(unit_json.json),反序列化会使用到unit_json.json去辅助生成Unit对象填入相应的Room对象的Unit列表。

4、可搜索#region 世界 房间 单元 实体 找到World、Room、Unit类。
        注意使用了[JsonIgnore]忽略某些字段序列化,以及使用[JsonConstructor]强制使用默认类去反序列化时的构造方法。个人认为反序列化时不应该依赖构造方法,而是手动组装所有类成员,曾试过会出问题,特别是有参构造方法,参数传入会是空的。

5、MoveGo方法,检测到地板物体后会拿到地板物体身上的Ground类对象,判断ground.IsCanPlaceBuild(movingGo)是否可放置在地板上,若可放置就调用ground.PlaceBuild(movingGo)放到地板,地板类会记录这个物体相关的信息用于判定是否可放置,若不可放置,那么会回到movingStartPos移动开始位置。

Ground.cs(地板类) 和 GroundData.cs(地板数据类)

Ground.cs持有1个GroundData.cs类构建一个以地板为中心的特殊空间。这个类没有太多作用,基本是与GroundData类对象沟通,用它只是用了一个Gizmos绘制地板的已占据格子。

案例中使用了一个Plane(灰色)地板以及一个Cube(绿色)地板,它们都挂载了Ground类。
每个Ground类是依据地板网格Mesh的Bounds.size和地板自身LocalScale大小相乘得到真实大小去构建GroundData的,并且以SlotSize切割出若干个格子Slot。


如上图,由于Plane网格是(10,1,10)大小,乘上Scale(100,1,100)得到(1000,1,1000)大小的GroundData,计算出的地板左下角,右上角坐标如上图(-500,-500), (500,500),总共会有1000*1000个Slot格子,注意Slot格子是动态创建的。

那么此时HGround物体的GroundData为(1000x1000)的平面,内有1000000个Slot。
为了支持Slot是动态创建的,GroundData存储Slot是使用Dictionary<int, SlotData> map字典,字典Key是由(y*width+x)构成。

重点分析方法:
1、IsCanPlaceBuild(GameObject go, Action<SlotData> action = null)是否可放置物体,action委托方法是处理一个将可放置Slot对象的方法,PlaceBuild方法会使用到这个。

        将go物体的世界坐标点转为地板局部坐标,会得到一个[-5,5]范围内的坐标,但我们需要的是一个[-500,500]的以上面的HGround大小为例,所以需要【局部坐标】乘以地板的LocalScale,得到一个【相对Ground平面的物体坐标】,之后会使用这个坐标和根据移动物体大小计算出【相对Ground平米的物体MinX,MaxX,MinY,MaxY边界值】,用这4个边界值分别处于SlotSize取整才能得到GroundData的下标边界值【left, right, bottom, top】,遍历这个边界值范围动态获取或创建SlotData,判断SlotData有物体hasGo且物体的unitId不等于当前移动物体的unitId时,会立刻退出循环遍历,返回false不可放置,否则是可放置的,会执行action方法将SlotData传递出去处理,返回true可放置。(这里有好几个坐标系 下标概念 要好好理解下)

        创建SlotData时,其中的center格子中心点是世界坐标系的,主要用途是用于绘制Gizmos使用,它是通过将【相对Ground平面的坐标除以地板的LocalScale得到【局部坐标】再转世界坐标,具体代码说明:

float localPosY = groundSize.y / 2f + slotSize / 2f;
Vector3 localPos = new Vector3((x + 0.5f) * slotSize / localScale.x, 
localPosY / localScale.y, (y + 0.5f) * slotSize / localScale.z);
data.center = ground.TransformPoint(localPos);

localPosY是相对Ground坐标的格子Y轴偏移值,等于地板深度/2加上格子高度/2,因为地板是有深度的,例如使用Cube作为地板网格时,深度是Bounds.size.y*LocalScale.y,如果不偏移这个深度其格子会埋没在地板内。【localPosY是相对Ground平面的格子坐标Y值】
x,y坐标是GroundData的map下标,它是格子的左下角下标,需要+0.5偏移到中心点再乘以slotSize得到【相对Ground平面的格子坐标】,之后则是除以LocalScale得到【局部坐标】再转世界坐标。

2、PlaceBuild(GameObject go) 放置物体

        这个方法使用到了IsCanPlaceBuild方法判定是否可放置,且传了action委托方法,将所有物体相关的可放入SlotData存储如List<SlotData> tempSlotDataList,确定是可放置后会遍历tempSlotDataList列表将所有SlotData的hasGo设置为True,unitId设置为当前物体unitId。
放置后会将<当前物体,tempSlotDataList>存储入字典cacheGoSlotDataDict,用于Gizmos绘制红色方框代表已放置的格子。

3、OnDrawGizmos方法 绘制所占据格子的红色方框

//通过rotationMatrix4矩阵将空间转到以绘制物体点为中心并且旋转角度保持与Ground一致的空间 直接进行原点绘制格子
Matrix4x4 oldMatrix4 = Handles.matrix;
Transform groundTrans = slotData.groundData.ground.transform;
Matrix4x4 rotationMatrix4 = Matrix4x4.TRS(slotData.center, Quaternion.FromToRotation(Vector3.up, groundTrans.up.normalized), Vector3.one);
Gizmos.matrix = rotationMatrix4; //转移矩阵
Gizmos.DrawWireCube(Vector3.zero, new Vector3(slotSize, slotSize, slotSize));
Gizmos.matrix = oldMatrix4; //复原矩阵

可优化Room也可以使用动态形式场景,类似SlotData一样的方式即可。

额外知识点说明

1、MeshFilter和MeshRenderer的Bounds区别

MeshFilter.mesh.bounds是网格的AABB盒,其大小和位置均是网格实际大小位置。
MeshRenderer.bounds是场景物体的AABB盒,其大小和位置会随受物体的TRS矩阵影响,即位移、旋转、缩放影响。

如上图,立方体(1,1,1)大小,MeshFilter是前三行数据,MeshRenderer是后三行数据,所以当你想获取物体的真实大小时,你应该用MeshFilter的形式获取Mesh.bounds知道它的大小,再乘上它的LocalScale得到,上面的代码如下

Debug.Log(GetComponent<MeshFilter>().mesh.bounds);
Debug.Log(GetComponent<MeshFilter>().mesh.bounds.center);
Debug.Log(GetComponent<MeshFilter>().mesh.bounds.size);Debug.Log("  ");
Debug.Log(GetComponent<MeshRenderer>().bounds);
Debug.Log(GetComponent<MeshRenderer>().bounds.center);
Debug.Log(GetComponent<MeshRenderer>().bounds.size);

 2、Gizmos 绘制一个平行于斜面的立方体

例如这个小方块的位置画一个和它一样重叠的红色线框方框,你会发现没有旋转。

你必须使用Gizmos.matrix去将空间转以这个绘制方块为中心的空间,再进行绘制

using UnityEditor;
using UnityEngine;public class Test : MonoBehaviour
{public Transform ground;private void OnDrawGizmos(){Gizmos.color = Color.red;//Gizmos.DrawWireCube(transform.position, transform.localScale);Matrix4x4 oldMatrix4 = Gizmos.matrix;Matrix4x4 rotationMatrix4 = Matrix4x4.TRS(transform.position,Quaternion.FromToRotation(Vector3.up, ground.up.normalized), Vector3.one);Gizmos.matrix = rotationMatrix4; //转移矩阵Gizmos.DrawWireCube(Vector3.zero, transform.localScale);Gizmos.matrix = oldMatrix4; //复原矩阵}
}

可能会有一些Bug,例如销毁物体时,MarkPlaceLight物体需要移出去,还有销毁物体时要将相关联的Unit、SlotData移除之类的操作没有做的,所以这方面的代码请自行修复吧...

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/6549.html

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

相关文章

高效沟通驱动LabVIEW项目成功

在LabVIEW项目开发中&#xff0c;由于涉及软件、硬件及多方协作&#xff0c;项目沟通效率的高低直接影响开发进度与最终质量。不明确的需求、信息传递中的误解以及跨部门协作的阻碍&#xff0c;常导致项目延误甚至失败。因此&#xff0c;建立高效的沟通机制&#xff0c;确保信息…

信息收集(下)

一.端口信息收集 1.端口基础认知 A.端口简介 在 Internet 环境中&#xff0c;各主机依据 TCP/IP 协议实现数据包的发送与接收。数据包凭借目的主机的 IP 地址&#xff0c;在互联网络里完成路由选择&#xff0c;进而精准地传至目标主机。然而&#xff0c;当目的主机同时运行多…

springBoot 整合ModBus TCP

ModBus是什么&#xff1a; ModBus是一种串行通信协议&#xff0c;主要用于从仪器和控制设备传输信号到主控制器或数据采集系统&#xff0c;例如用于测量温度和湿度并将结果传输到计算机的系统。&#xff08;百度答案&#xff09; ModBus 有些什么东西&#xff1a; ModBus其分…

composer安装指定php版本, 忽略平台原因导致的报错

windows下 //composer安装指定php版本, 写出完整的php和composer.phar路径 D:\phpstudy_pro\Extensions\php\php8.1.11nts\php.exe D:\phpstudy_pro\Extensions\composer1.8.5\composer.phar install windows下一些扩展不支持, 如下图, 所以本地composer安装组件时可以忽略 …

【论文投稿】Python 网络爬虫:探秘网页数据抓取的奇妙世界

目录 前言 一、Python—— 网络爬虫的绝佳拍档 二、网络爬虫基础&#xff1a;揭开神秘面纱 &#xff08;一&#xff09;工作原理&#xff1a;步步为营的数据狩猎 &#xff08;二&#xff09;分类&#xff1a;各显神通的爬虫家族 三、Python 网络爬虫核心库深度剖析 &…

大模型应用与部署 技术方案

大模型应用与部署 技术方案 一、引言 人工智能蓬勃发展,Qwen 大模型在自然语言处理领域地位关键,其架构优势尽显,能处理文本创作等多类复杂任务,提供优质交互。Milvus 向量数据库则是向量数据存储检索利器,有高效索引算法(如 IVF_FLAT、HNSWLIB 等)助力大规模数据集相似…

Postman接口测试工具详解

(创作不易&#xff0c;感谢有你&#xff0c;你的支持&#xff0c;就是我前行的最大动力&#xff0c;如果看完对你有帮助&#xff0c;还请三连支持一波哇ヾ(&#xff20;^∇^&#xff20;)ノ&#xff09; 目录 引言 Postman简介 Postman的特点 Postman的下载与安装 Postman…

电路研究9.2——合宙Air780EP使用AT指令

这里正式研究AT指令的学习了&#xff0c;之前只是接触的AT指令&#xff0c;这里则是深入分析AT指令了。 软件的开发方式&#xff1a; AT&#xff1a;MCU 做主控&#xff0c;MCU 发 AT 命令给模组的开发方式&#xff0c;模组仅提供标准的 AT 固件&#xff0c; 所有的业务控制逻辑…

百度APP iOS端磁盘优化实践(上)

01 概览 在APP的开发中&#xff0c;磁盘管理已成为不可忽视的部分。随着功能的复杂化和数据量的快速增长&#xff0c;如何高效管理磁盘空间直接关系到用户体验和APP性能。本文将结合磁盘管理的实践经验&#xff0c;详细介绍iOS沙盒环境下的文件存储规范&#xff0c;探讨业务缓…

Sharding-JDBC 5.4.1+SpringBoot3.4.1+MySQL8.4.1 使用案例

最近在升级 SpringBoot 项目&#xff0c;原版本是 2.7.16&#xff0c;要升级到 3.4.0 &#xff0c;JDK 版本要从 JDK8 升级 JDK21&#xff0c;原项目中使用了 Sharding-JDBC&#xff0c;版本 4.0.0-RC1&#xff0c;在升级 SpringBoot 版本到 3.4.0 之后&#xff0c;服务启动失败…

【Django】多个APP设置独立的URL

目录 方法一&#xff1a;各个App下设置自己的URL 1、在各自的App当中创建urls.py文件​编辑 2、在主urls当中包含子url 3、各App的urls中设置url 4、设置后台函数 5、最终结果 总结&#xff1a; 方法二&#xff1a;利用as方法&#xff0c;在总的URL中对views重命名 实…

Vue2 项目二次封装Axios

引言 在现代前端开发中&#xff0c;HTTP请求管理是构建健壮应用的核心能力之一。Axios作为目前最流行的HTTP客户端库&#xff0c;其灵活性和可扩展性为开发者提供了强大的基础能力。 1. 为什么要二次封装Axios&#xff1f; 1.1 统一项目管理需求 API路径标准化&#xff1a;…

【算法】经典博弈论问题——巴什博弈 python

目录 前言巴什博弈(Bash Game)小试牛刀PN分析实战检验总结 前言 博弈类问题大致分为&#xff1a; 公平组合游戏、非公平组合游戏&#xff08;绝大多数的棋类游戏&#xff09;和 反常游戏 巴什博弈(Bash Game) 一共有n颗石子&#xff0c;两个人轮流拿&#xff0c;每次可以拿1~m颗…

软件开发学习路线——roadmap

推荐软件学习路线网站&#xff1a;https://roadmap.sh/get-started 有有关前端后端开发的学习路径&#xff0c;也有AI&#xff0c;移动开发&#xff0c;管理相关的学习路径 会有相应的词条路径&#xff0c;深入学习 右上角可以设置学习任务的完成情况

Moretl FileSync增量文件采集工具

永久免费: <下载> <使用说明> 我们希望Moretl FileSync是一款通用性很好的文件日志采集工具,解决工厂环境下,通过共享目录采集文件,SMB协议存在的安全性,兼容性的问题. 同时,我们发现工厂设备日志一般为增量,为方便MES,QMS等后端系统直接使用数据,我们推出了增量采…

9、Docker环境安装Nginx

一、拉取镜像 docker pull nginx:1.24.0二、创建映射目录 作用&#xff1a;是将docker中nginx的相关配置信息映射到外面&#xff0c;方便修改配置文件 1、创建目录 # cd home/ # mkdir nginx/ # cd nginx/ # mkdir conf html log2、生成容器 docker run -p 80:80 -d --name…

023:到底什么是感受野?

本文为合集收录&#xff0c;欢迎查看合集/专栏链接进行全部合集的系统学习。 合集完整版请查看这里。 在前面介绍卷积算法时&#xff0c;一直在强调一个内容&#xff0c;那就是卷积算法的运算过程是—— 卷积核在输入图像上滑动扫描的过程。 在每一次扫描时&#xff0c;可以…

BGP(1)邻居建立,路由宣告

拓扑如图&#xff0c;配置地址&#xff0c;配置ospf并宣告相应地址 1、观察bgp邻居的建立 a R1和R3建立bgp邻居 抓包可以看到TCP的三次握手&#xff0c;端口号179 可以看到R1和R3成功建立了IBGP邻居 b 缺省情况下&#xff0c;BGP使用报文出接口作为TCP连接的本地接口&#x…

Python 预训练:打通视觉与大语言模型应用壁垒——Python预训练视觉和大语言模型

大语言模型是一种由包含数百亿甚至更多参数的深度神经网络构建的语言模型&#xff0c;通常使用自监督学习方法通过大量无标签文本进行训练&#xff0c;是深度学习之后的又一大人工智能技术革命。 大语言模型的发展主要经历了基础模型阶段(2018 年到2021年)、能力探索阶段(2019年…

【数据库】详解MySQL数据库中的事务与锁

目录 1.数据库事务 1.1.事务的四大特性 1.2.事务开启的方式 1.3.读一致性问题及其解决 2.MVCC解决读一致性问题原理 2.1.MVCC概念 2.2.准备环境 3.MySQL中的锁 3.1.行锁之共享锁 3.2.行锁之排它锁 1.数据库事务 数据库事务&#xff08;Transaction&#xff09;是一种…