【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移除之类的操作没有做的,所以这方面的代码请自行修复吧...
相关文章:
【Unity3D】3D物体摆放、场景优化案例Demo
目录 PlaceManager.cs(放置管理类) Ground.cs(地板类) 和 GroundData.cs(地板数据类) 额外知识点说明 1、MeshFilter和MeshRenderer的Bounds区别 2、Gizmos 绘制一个平行于斜面的立方体 通过网盘分享的文件:PlaceGameDemo2.unitypackage 链接: https://pan.baid…...
使用HTML5 Canvas 实现呼吸粒子球动画效果的原理
在网页开发领域,动画效果能够极大地提升用户体验,让页面变得更加生动有趣。今天,我们深入剖析一个基于 HTML5 Canvas 的 3D 粒子动画 —— 呼吸粒子球。通过详细解读其代码实现,我们将全面了解如何运用 HTML5 的强大功能构建出如此…...
Java 中实体类与操作类分离
目录 一、为啥要把实体类和操作类分开 二、实体类长啥样,怎么用 三、操作类的使命与实现 四、实战演练:实体类与操作类协同工作 五、拓展思考:这种分离带来的好处与进一步优化 六、总结与展望 家人们,今天我想跟你们唠唠我在…...
【STM32HAL-----GPIO】
1. 什么是GPIO?(了解) 2. STM32 GPIO简介 2.1. GPIO特点 2.2. GPIO电气特性 2.3. GPIO引脚分布图 IO引脚分布特点:按组存在、组数视芯片而定、每组最多16个IO引脚。 3. IO端口基本结构介绍 4. GPIO八种工作模式 4.1. 输入浮空 特…...
Java Web开发高级——单元测试与集成测试
测试是软件开发的重要环节,确保代码质量和功能的正确性。在Spring Boot项目中,单元测试和集成测试是常用的两种测试类型: 单元测试:测试单个模块(如类或方法)是否按预期工作。集成测试:测试多个…...
编译chromium笔记
编译环境: windows10 powershell7.2.24 git 2.47.1 https://storage.googleapis.com/chrome-infra/depot_tools.zip 配置git git config --global user.name "John Doe" git config --global user.email "jdoegmail.com" git config --global …...
Web开发 -前端部分-CSS3新特性
1 CSS概述 2 CSS3私有前缀 3 CSS3的长度单位 代码实现: <!DOCTYPE html> <html lang"zh-CN"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"…...
【基础篇】什么是SQL注入,如何防止?
什么是 SQL 注入,如何防止? SQL 注入(SQL Injection)是一种常见的网络安全漏洞,它发生在 Web 应用程序中,当恶意用户在输入数据时,将恶意的 SQL 代码插入到输入中,从而导致应用程序…...
Swift语言的数据结构
Swift语言的数据结构 Swift是一种现代化的编程语言,它以安全性、性能和简洁性著称。尽管Swift通常被视为面向对象的语言,但它也支持函数式编程的特性,使得开发者可以以多种方式构建应用程序。在Swift中,数据结构是编程的基础&…...
牛客周赛 Round 77
题目链接:牛客周赛 Round 77 A. 时间表 tag:签到 B. 数独数组 tag:签到 Description:给定n个数,每个数的范围为1-9,问能否经过排列,使其每个长度为9的连续子数组都包含1-9这9个数字。 Sol…...
浅谈云端编辑器,分析其亮点与不足
浅谈云端编辑器,分析其亮点与不足 这个云端编辑器界面可以分为左侧题目筛选栏、中间题目描述与代码编辑区域、右侧AI提示功能三部分。以下是详细的分析: 1. 左侧题目筛选栏 层次结构清晰:左侧栏展示了一个层级结构,题目按主题分…...
web应用引入cookie机制的用途和cookie技术主要包括的内容
web应用引入cookie机制,用于用户跟踪。 (1)HTTP响应报文中的Cookie头行:set-Cookie (2)用户浏览器在本地存储、维护和管理的Cookie文件 (3)HTTP请求报文中的Cookie头行:…...
【HTML+CSS】使用HTML与后端技术连接数据库
目录 一、概述 1.1 HTML前端 1.2 后端技术 1.3 数据库 二、HTML表单示例 三、PHP后端示例 3.1 连接数据库 3.2 接收数据并插入数据库 四、安全性 4.1 防止SQL注入 4.2 数据验证与清洗 五、优化 5.1 索引优化 5.2 查询优化 六、现代Web开发中的最佳实践 6.1 使用…...
「2024·我的成长之路」:年终反思与展望
文章目录 1. 前言2.创作历程2.1 摆烂期2.2 转变期3. 上升期 2. 个人收获3.经验分享4. 展望未来 1. 前言 2025年1月16日,2024年博客之星入围公布,很荣幸获得了这次入围的机会。2024年对我个人是里程碑的一年,是意义非凡的一年,是充…...
C#PaddleOCRSharp使用
using PaddleOCRSharp;namespace PaddleOCRSharpDemo {internal class Program{static void Main(string[] args){//中英文模型V3模型OCRModelConfig config null;//OCR参数OCRParameter oCRParameter new OCRParameter();oCRParameter.cpu_math_library_num_threads 6;//预…...
【Excel】【VBA】Reaction超限点筛选与散点图可视化
【Excel】【VBA】Reaction超限点筛选与散点图可视化 功能概述 这段代码实现了以下功能: 从SAFE输出的结果worksheet通过datalink获取更新数据从指定工作表中读取数据检测超过阈值的数据点生成结果表格并添加格式化创建可视化散点图显示执行时间 流程图 #mermaid-…...
京华春梦,守岁这方烟火人间
文章目录 准备篇温度公共交通人流情况年货采购 文化体验传统庙会博物馆与展览烟花灯会祈福仪式民俗集市现代氛围其他活动 美食盛宴传统美食与特色小吃传统老字号京城新宠特色小吃街多元美食街 准备篇 温度 北京春节期间气温较低,室外通常在零下几度到零上几度之间…...
学Python的人…
学Python的人… 一、Python能干什么? 1.爬虫:前几年,深度学习还没发展起来的时候,书店里Python就和爬虫挂钩,因为Python写爬虫确实方便。 2.数据分析:Python有各种的数据分析库可以方便使用࿰…...
WebSocket 和 Socket 的区别
一、协议层次和工作方式 1.1 )Socket 1.1.1)Socket位于传输层,通常使用TCP或UDP协议 1.1.2)提供了一个通用的网络编程接口,允许应用程序通过它发送和接收数据 1.1.3)一般需要手动管理连接,错…...
学习ASP.NET Core的身份认证(基于JwtBearer的身份认证6)
重新创建WebApi项目,安装Microsoft.AspNetCore.Authentication.JwtBearer包,将之前JwtBearer测试项目中的初始化函数,jwt配置类、token生成类全部挪到项目中。 重新编写login函数,之前测试Cookie和Session认证时用的函数适合m…...
后进先出(LIFO)详解
LIFO 是 Last In, First Out 的缩写,中文译为后进先出。这是一种数据结构的工作原则,类似于一摞盘子或一叠书本: 最后放进去的元素最先出来 -想象往筒状容器里放盘子: (1)你放进的最后一个盘子(…...
解锁数据库简洁之道:FastAPI与SQLModel实战指南
在构建现代Web应用程序时,与数据库的交互无疑是核心环节。虽然传统的数据库操作方式(如直接编写SQL语句与psycopg2交互)赋予了我们精细的控制权,但在面对日益复杂的业务逻辑和快速迭代的需求时,这种方式的开发效率和可…...
MODBUS TCP转CANopen 技术赋能高效协同作业
在现代工业自动化领域,MODBUS TCP和CANopen两种通讯协议因其稳定性和高效性被广泛应用于各种设备和系统中。而随着科技的不断进步,这两种通讯协议也正在被逐步融合,形成了一种新型的通讯方式——开疆智能MODBUS TCP转CANopen网关KJ-TCPC-CANP…...
【论文阅读28】-CNN-BiLSTM-Attention-(2024)
本文把滑坡位移序列拆开、筛优质因子,再用 CNN-BiLSTM-Attention 来动态预测每个子序列,最后重构出总位移,预测效果超越传统模型。 文章目录 1 引言2 方法2.1 位移时间序列加性模型2.2 变分模态分解 (VMD) 具体步骤2.3.1 样本熵(S…...
【Oracle】分区表
个人主页:Guiat 归属专栏:Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...
Java编程之桥接模式
定义 桥接模式(Bridge Pattern)属于结构型设计模式,它的核心意图是将抽象部分与实现部分分离,使它们可以独立地变化。这种模式通过组合关系来替代继承关系,从而降低了抽象和实现这两个可变维度之间的耦合度。 用例子…...
20个超级好用的 CSS 动画库
分享 20 个最佳 CSS 动画库。 它们中的大多数将生成纯 CSS 代码,而不需要任何外部库。 1.Animate.css 一个开箱即用型的跨浏览器动画库,可供你在项目中使用。 2.Magic Animations CSS3 一组简单的动画,可以包含在你的网页或应用项目中。 3.An…...
【无标题】路径问题的革命性重构:基于二维拓扑收缩色动力学模型的零点隧穿理论
路径问题的革命性重构:基于二维拓扑收缩色动力学模型的零点隧穿理论 一、传统路径模型的根本缺陷 在经典正方形路径问题中(图1): mermaid graph LR A((A)) --- B((B)) B --- C((C)) C --- D((D)) D --- A A -.- C[无直接路径] B -…...
Java数值运算常见陷阱与规避方法
整数除法中的舍入问题 问题现象 当开发者预期进行浮点除法却误用整数除法时,会出现小数部分被截断的情况。典型错误模式如下: void process(int value) {double half = value / 2; // 整数除法导致截断// 使用half变量 }此时...
uniapp 开发ios, xcode 提交app store connect 和 testflight内测
uniapp 中配置 配置manifest 文档:manifest.json 应用配置 | uni-app官网 hbuilderx中本地打包 下载IOS最新SDK 开发环境 | uni小程序SDK hbulderx 版本号:4.66 对应的sdk版本 4.66 两者必须一致 本地打包的资源导入到SDK 导入资源 | uni小程序SDK …...
