当前位置: 首页 > news >正文

【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 绘制一个平行于斜面的立方体 通过网盘分享的文件&#xff1a;PlaceGameDemo2.unitypackage 链接: https://pan.baid…...

使用HTML5 Canvas 实现呼吸粒子球动画效果的原理

在网页开发领域&#xff0c;动画效果能够极大地提升用户体验&#xff0c;让页面变得更加生动有趣。今天&#xff0c;我们深入剖析一个基于 HTML5 Canvas 的 3D 粒子动画 —— 呼吸粒子球。通过详细解读其代码实现&#xff0c;我们将全面了解如何运用 HTML5 的强大功能构建出如此…...

Java 中实体类与操作类分离

目录 一、为啥要把实体类和操作类分开 二、实体类长啥样&#xff0c;怎么用 三、操作类的使命与实现 四、实战演练&#xff1a;实体类与操作类协同工作 五、拓展思考&#xff1a;这种分离带来的好处与进一步优化 六、总结与展望 家人们&#xff0c;今天我想跟你们唠唠我在…...

【STM32HAL-----GPIO】

1. 什么是GPIO&#xff1f;&#xff08;了解&#xff09; 2. STM32 GPIO简介 2.1. GPIO特点 2.2. GPIO电气特性 2.3. GPIO引脚分布图 IO引脚分布特点&#xff1a;按组存在、组数视芯片而定、每组最多16个IO引脚。 3. IO端口基本结构介绍 4. GPIO八种工作模式 4.1. 输入浮空 特…...

Java Web开发高级——单元测试与集成测试

测试是软件开发的重要环节&#xff0c;确保代码质量和功能的正确性。在Spring Boot项目中&#xff0c;单元测试和集成测试是常用的两种测试类型&#xff1a; 单元测试&#xff1a;测试单个模块&#xff08;如类或方法&#xff09;是否按预期工作。集成测试&#xff1a;测试多个…...

编译chromium笔记

编译环境&#xff1a; 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的长度单位 代码实现&#xff1a; <!DOCTYPE html> <html lang"zh-CN"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"…...

【基础篇】什么是SQL注入,如何防止?

什么是 SQL 注入&#xff0c;如何防止&#xff1f; SQL 注入&#xff08;SQL Injection&#xff09;是一种常见的网络安全漏洞&#xff0c;它发生在 Web 应用程序中&#xff0c;当恶意用户在输入数据时&#xff0c;将恶意的 SQL 代码插入到输入中&#xff0c;从而导致应用程序…...

Swift语言的数据结构

Swift语言的数据结构 Swift是一种现代化的编程语言&#xff0c;它以安全性、性能和简洁性著称。尽管Swift通常被视为面向对象的语言&#xff0c;但它也支持函数式编程的特性&#xff0c;使得开发者可以以多种方式构建应用程序。在Swift中&#xff0c;数据结构是编程的基础&…...

牛客周赛 Round 77

题目链接&#xff1a;牛客周赛 Round 77 A. 时间表 tag&#xff1a;签到 B. 数独数组 tag&#xff1a;签到 Description&#xff1a;给定n个数&#xff0c;每个数的范围为1-9&#xff0c;问能否经过排列&#xff0c;使其每个长度为9的连续子数组都包含1-9这9个数字。 Sol…...

浅谈云端编辑器,分析其亮点与不足

浅谈云端编辑器&#xff0c;分析其亮点与不足 这个云端编辑器界面可以分为左侧题目筛选栏、中间题目描述与代码编辑区域、右侧AI提示功能三部分。以下是详细的分析&#xff1a; 1. 左侧题目筛选栏 层次结构清晰&#xff1a;左侧栏展示了一个层级结构&#xff0c;题目按主题分…...

web应用引入cookie机制的用途和cookie技术主要包括的内容

web应用引入cookie机制&#xff0c;用于用户跟踪。 &#xff08;1&#xff09;HTTP响应报文中的Cookie头行&#xff1a;set-Cookie &#xff08;2&#xff09;用户浏览器在本地存储、维护和管理的Cookie文件 &#xff08;3&#xff09;HTTP请求报文中的Cookie头行&#xff1a;…...

【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日&#xff0c;2024年博客之星入围公布&#xff0c;很荣幸获得了这次入围的机会。2024年对我个人是里程碑的一年&#xff0c;是意义非凡的一年&#xff0c;是充…...

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超限点筛选与散点图可视化 功能概述 这段代码实现了以下功能&#xff1a; 从SAFE输出的结果worksheet通过datalink获取更新数据从指定工作表中读取数据检测超过阈值的数据点生成结果表格并添加格式化创建可视化散点图显示执行时间 流程图 #mermaid-…...

京华春梦,守岁这方烟火人间

文章目录 准备篇温度公共交通人流情况年货采购 文化体验传统庙会博物馆与展览烟花灯会祈福仪式民俗集市现代氛围其他活动 美食盛宴传统美食与特色小吃传统老字号京城新宠特色小吃街多元美食街 准备篇 温度 北京春节期间气温较低&#xff0c;室外通常在零下几度到零上几度之间…...

学Python的人…

学Python的人… 一、Python能干什么&#xff1f; 1.爬虫&#xff1a;前几年&#xff0c;深度学习还没发展起来的时候&#xff0c;书店里Python就和爬虫挂钩&#xff0c;因为Python写爬虫确实方便。 2.数据分析&#xff1a;Python有各种的数据分析库可以方便使用&#xff0…...

WebSocket 和 Socket 的区别

一、协议层次和工作方式 1.1 &#xff09;Socket 1.1.1&#xff09;Socket位于传输层&#xff0c;通常使用TCP或UDP协议 1.1.2&#xff09;提供了一个通用的网络编程接口&#xff0c;允许应用程序通过它发送和接收数据 1.1.3&#xff09;一般需要手动管理连接&#xff0c;错…...

学习ASP.NET Core的身份认证(基于JwtBearer的身份认证6)

重新创建WebApi项目&#xff0c;安装Microsoft.AspNetCore.Authentication.JwtBearer包&#xff0c;将之前JwtBearer测试项目中的初始化函数&#xff0c;jwt配置类、token生成类全部挪到项目中。   重新编写login函数&#xff0c;之前测试Cookie和Session认证时用的函数适合m…...

label-studio的使用教程(导入本地路径)

文章目录 1. 准备环境2. 脚本启动2.1 Windows2.2 Linux 3. 安装label-studio机器学习后端3.1 pip安装(推荐)3.2 GitHub仓库安装 4. 后端配置4.1 yolo环境4.2 引入后端模型4.3 修改脚本4.4 启动后端 5. 标注工程5.1 创建工程5.2 配置图片路径5.3 配置工程类型标签5.4 配置模型5.…...

Spark 之 入门讲解详细版(1)

1、简介 1.1 Spark简介 Spark是加州大学伯克利分校AMP实验室&#xff08;Algorithms, Machines, and People Lab&#xff09;开发通用内存并行计算框架。Spark在2013年6月进入Apache成为孵化项目&#xff0c;8个月后成为Apache顶级项目&#xff0c;速度之快足见过人之处&…...

五年级数学知识边界总结思考-下册

目录 一、背景二、过程1.观察物体小学五年级下册“观察物体”知识点详解&#xff1a;由来、作用与意义**一、知识点核心内容****二、知识点的由来&#xff1a;从生活实践到数学抽象****三、知识的作用&#xff1a;解决实际问题的工具****四、学习的意义&#xff1a;培养核心素养…...

ServerTrust 并非唯一

NSURLAuthenticationMethodServerTrust 只是 authenticationMethod 的冰山一角 要理解 NSURLAuthenticationMethodServerTrust, 首先要明白它只是 authenticationMethod 的选项之一, 并非唯一 1 先厘清概念 点说明authenticationMethodURLAuthenticationChallenge.protectionS…...

Axios请求超时重发机制

Axios 超时重新请求实现方案 在 Axios 中实现超时重新请求可以通过以下几种方式&#xff1a; 1. 使用拦截器实现自动重试 import axios from axios;// 创建axios实例 const instance axios.create();// 设置超时时间 instance.defaults.timeout 5000;// 最大重试次数 cons…...

Unit 1 深度强化学习简介

Deep RL Course ——Unit 1 Introduction 从理论和实践层面深入学习深度强化学习。学会使用知名的深度强化学习库&#xff0c;例如 Stable Baselines3、RL Baselines3 Zoo、Sample Factory 和 CleanRL。在独特的环境中训练智能体&#xff0c;比如 SnowballFight、Huggy the Do…...

大语言模型(LLM)中的KV缓存压缩与动态稀疏注意力机制设计

随着大语言模型&#xff08;LLM&#xff09;参数规模的增长&#xff0c;推理阶段的内存占用和计算复杂度成为核心挑战。传统注意力机制的计算复杂度随序列长度呈二次方增长&#xff0c;而KV缓存的内存消耗可能高达数十GB&#xff08;例如Llama2-7B处理100K token时需50GB内存&a…...

基于IDIG-GAN的小样本电机轴承故障诊断

目录 🔍 核心问题 一、IDIG-GAN模型原理 1. 整体架构 2. 核心创新点 (1) ​梯度归一化(Gradient Normalization)​​ (2) ​判别器梯度间隙正则化(Discriminator Gradient Gap Regularization)​​ (3) ​自注意力机制(Self-Attention)​​ 3. 完整损失函数 二…...

Docker拉取MySQL后数据库连接失败的解决方案

在使用Docker部署MySQL时&#xff0c;拉取并启动容器后&#xff0c;有时可能会遇到数据库连接失败的问题。这种问题可能由多种原因导致&#xff0c;包括配置错误、网络设置问题、权限问题等。本文将分析可能的原因&#xff0c;并提供解决方案。 一、确认MySQL容器的运行状态 …...

Linux安全加固:从攻防视角构建系统免疫

Linux安全加固:从攻防视角构建系统免疫 构建坚不可摧的数字堡垒 引言:攻防对抗的新纪元 在日益复杂的网络威胁环境中,Linux系统安全已从被动防御转向主动免疫。2023年全球网络安全报告显示,高级持续性威胁(APT)攻击同比增长65%,平均入侵停留时间缩短至48小时。本章将从…...