【unity实战】实现一个放置3d物品建造装修系统(附项目源码)

文章目录

  • 最终效果
  • 前言
  • 绘制开始场景
  • 素材
  • 开始
  • 放置
  • 旋转物体
  • 扩展优化
    • 1. 绘制地图边界,确保放置物品在指定区域内工作
    • 2. 让模型所占面积大小更加准确
    • 3. 隐藏白色瓦片指示区域
  • 最终效果
  • 其他
  • 源码
  • 参考
  • 完结

最终效果

在这里插入图片描述

前言

其实3d物品建造装修系统之前就已经做过了,感兴趣的可以去看看:手搓一个网格放置功能,及装修建造种植功能

但是它有一些缺点,比如网格是自己绘制的,使用起来可能比较麻烦,所有这里分享另一种更加简单的方法。就是使用tilemap,可以省略自己绘制复杂网格的时间,但是缺点可能就是玩家无法在游戏界面看到网格的具体位置,当然,实现功能千千万万,选择自己喜欢的就行。

绘制开始场景

在这里插入图片描述
在平台上放置tilemap,并配置对应参数
在这里插入图片描述
在这里插入图片描述

简单绘制,效果
在这里插入图片描述

素材

可以寻找下载你喜欢的模型,导入到项目中

这里我推荐个地址
https://sketchfab.com/Cytiene/collections/great-downloadable-models-6304c532e52649f59de0de234edcb91f
在这里插入图片描述

开始

新增可放置对象脚本PlaceableObject ,暂时什么都不做

public class PlaceableObject : MonoBehaviour { }

所有模型物品都挂载脚本,并给模型添加碰撞体
在这里插入图片描述

新增BuildingSystem,定义一个建筑系统的脚本

public class BuildingSystem : MonoBehaviour
{public static BuildingSystem current;public GridLayout gridLayout;private Grid grid;[SerializeField] private Tilemap mainTilemap; // 地图的Tilemap组件[SerializeField] private TileBase whiteTile; // 白色方块的TileBasepublic GameObject prefab1; // 预制体1public GameObject prefab2; // 预制体2private PlaceableObject objectToPlace; // 当前要放置的对象private void Awake(){current = this;grid = gridLayout.gameObject.GetComponent<Grid>(); // 获取网格组件}//测试切换不同模型物品private void Update(){if (Input.GetKeyDown(KeyCode.A)){InitializeWithObject(prefab1);}else if (Input.GetKeyDown(KeyCode.B)){InitializeWithObject(prefab2);}}// 工具方法:将鼠标位置转换为世界坐标系下的位置public static Vector3 GetMouseWorldPosition(){// 从相机发出一条射线,将鼠标位置转换为世界坐标系下的位置Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);// 如果射线碰撞到物体,则返回碰撞点的世界坐标if (Physics.Raycast(ray, out RaycastHit raycastHit)){return raycastHit.point;}else // 否则返回零向量{return Vector3.zero;}}// 将坐标对齐到网格上public Vector3 SnapCoordinateToGrid(Vector3 position){Vector3Int cellPos = gridLayout.WorldToCell(position); // 将世界坐标转换为网格单元坐标position = grid.GetCellCenterWorld(cellPos); // 获取网格单元中心点的世界坐标return position;}//初始化放置物体public void InitializeWithObject(GameObject prefab){// 将物体初始位置设为网格对齐的原点Vector3 position = SnapCoordinateToGrid(Vector3.zero);// 在初始位置实例化物体GameObject obj = Instantiate(prefab, position, Quaternion.identity);// 获取PlaceableObject组件并添加ObjectDrag组件objectToPlace = obj.GetComponent<PlaceableObject>(); // 获取可放置物体组件obj.AddComponent<ObjectDrag>(); // 添加拖拽组件}
}

新增ObjectDrag,定义一个物体拖拽的脚本,注意物品移动要有碰撞体,不然拖拽不会生效

public class ObjectDrag : MonoBehaviour
{private Vector3 offset; // 鼠标按下时物体和鼠标之间的偏移量// 当鼠标按下时记录偏移量private void OnMouseDown(){offset = transform.position - BuildingSystem.GetMouseWorldPosition();}// 当鼠标拖动时移动物体并对齐到网格上private void OnMouseDrag(){Vector3 pos = BuildingSystem.GetMouseWorldPosition() + offset;transform.position = BuildingSystem.current.SnapCoordinateToGrid(pos);}
}

挂载脚本配置
在这里插入图片描述
效果,按AB生成不同的物品,点击物品可以进行拖拽
在这里插入图片描述
当然,你也可以修改ObjectDrag,直接使用Update方法,让物品一直跟随鼠标移动

// 每帧更新建筑物的位置
private void Update()
{Vector3 pos = BuildingSystem.GetMouseWorldPosition() + offset;transform.position = BuildingSystem.current.SnapCoordinateToGrid(pos);
}

放置

修改PlaceableObject

public class PlaceableObject : MonoBehaviour
{// 是否已经放置public bool Placed { get; private set; }// 物体占据的格子数public Vector3Int Size { get; private set; }// 物体碰撞器的四个顶点(本地坐标系)private Vector3[] Vertices;private void Start(){// 获取物体碰撞器的四个顶点GetColliderVertexPositionsLocal();// 计算物体占据的格子数CalculateSizeInCells();}// 获取物体碰撞器的四个顶点private void GetColliderVertexPositionsLocal(){BoxCollider b = gameObject.GetComponent<BoxCollider>();Vertices = new Vector3[4];Vertices[0] = b.center + new Vector3(-b.size.x, -b.size.y, -b.size.z) * 0.5f;Vertices[1] = b.center + new Vector3(b.size.x, -b.size.y, -b.size.z) * 0.5f;Vertices[2] = b.center + new Vector3(b.size.x, -b.size.y, b.size.z) * 0.5f;Vertices[3] = b.center + new Vector3(-b.size.x, -b.size.y, b.size.z) * 0.5f;}// 计算物体占据的格子数private void CalculateSizeInCells(){Vector3Int[] vertices = new Vector3Int[Vertices.Length];for (int i = 0; i < vertices.Length; i++){// 将物体顶点从本地坐标系转换到世界坐标系Vector3 worldPos = transform.TransformPoint(Vertices[i]);// 将世界坐标系中的位置转换成格子坐标系中的位置vertices[i] = BuildingSystem.current.gridLayout.WorldToCell(worldPos);}// 计算物体占据的格子数Size = new Vector3Int(Mathf.Abs(vertices[0].x - vertices[1].x),Mathf.Abs(vertices[0].y - vertices[3].y),1);}// 获取物体的起始位置(左下角的格子位置)public Vector3 GetStartPosition(){return transform.TransformPoint(Vertices[0]);}// 放置物体public virtual void Place(){// 删除物体拖拽组件ObjectDrag drag = gameObject.GetComponent<ObjectDrag>();Destroy(drag);// 标记物体已经放置Placed = true;// TODO:触发放置事件}
}

修改BuildingSystem

private void Update()
{//。。。//放置测试if (Input.GetKeyDown(KeyCode.Space)){if (CanBePlaced(objectToPlace)) // 检查物体是否可以放置{objectToPlace.Place(); // 放置物体Vector3Int start = gridLayout.WorldToCell(objectToPlace.GetStartPosition()); // 将世界坐标转换为格子坐标TakeArea(start, objectToPlace.Size); // 将物体所占据的区域填充为白色瓦片}else{Destroy(objectToPlace.gameObject); // 物体无法放置,销毁物体}}else if (Input.GetKeyDown(KeyCode.Escape)){Destroy(objectToPlace.gameObject); // 按下 Esc 键,销毁物体}
}//获取一个区域内的瓦片信息数组
private static TileBase[] GetTilesBlock(BoundsInt area, Tilemap tilemap)
{TileBase[] array = new TileBase[area.size.x * area.size.y * area.size.z];int counter = 0;foreach (var v in area.allPositionsWithin){Vector3Int pos = new Vector3Int(v.x, v.y, 0);array[counter] = tilemap.GetTile(pos); // 获取指定位置上的瓦片counter++;}return array;
}//检查物体是否可以放置在指定位置
private bool CanBePlaced(PlaceableObject placeableObject)
{BoundsInt area = new BoundsInt();area.position = gridLayout.WorldToCell(placeableObject.GetStartPosition()); // 将世界坐标转换为格子坐标area.size = placeableObject.Size; // 获取物体所占据的格子大小TileBase[] baseArray = GetTilesBlock(area, mainTilemap); // 获取该区域内的瓦片数组foreach (var b in baseArray){if (b == whiteTile) // 如果有白色瓦片,表示物体无法放置{return false;}}return true; // 没有白色瓦片,可以放置物体
}//在指定区域填充为白色瓦片
public void TakeArea(Vector3Int start, Vector3Int size)
{mainTilemap.BoxFill(start, whiteTile, start.x, start.y, start.x + size.x, start.y + size.y); // 将指定区域填充为白色瓦片
}

效果,物体重叠会直接销毁物品
在这里插入图片描述

旋转物体

修改PlaceableObject

//旋转
public void Rotate()
{transform.Rotate(eulers: new Vector3(0, 90, 0)); // 绕 Y 轴顺时针旋转 90 度// 交换长宽并限制高度为 1Size = new Vector3Int(Size.y, Size.x, 1);// 旋转顶点数组Vector3[] vertices = new Vector3[Vertices.Length];for (int i = 0; i < vertices.Length; i++){vertices[i] = Vertices[(i + 1) % Vertices.Length]; // 将顶点数组顺时针旋转}Vertices = vertices; // 更新顶点数组
}

修改BuildingSystem,调用

private void Update()
{ //。。。//按回车旋转物体if (Input.GetKeyDown(KeyCode.Return)){objectToPlace.Rotate();}
}

效果
在这里插入图片描述

扩展优化

1. 绘制地图边界,确保放置物品在指定区域内工作

在建筑区域周围绘制瓷砖边框,这将增加图块地图边界效果,并确保放置物品在指定区域内工作。
在这里插入图片描述

2. 让模型所占面积大小更加准确

现在TileMap每格网格比较大,为了让模型所占面积大小更加准确,可以适当缩小Grid的比例
在这里插入图片描述

效果
在这里插入图片描述

3. 隐藏白色瓦片指示区域

实际使用我们肯定不想看到白色瓦片所显示的指示区域,我们可以关闭Tilemap Renderer,或者修改TileMap颜色透明的为0
在这里插入图片描述
效果
在这里插入图片描述

最终效果

在这里插入图片描述

其他

后续其他内容我就不继续完善了,留给大家自己去发挥,比如

  • 添加一些放置特效、动画、音效
  • 删除功能
  • 无法放置显示红色,未放置显示蓝色
  • 显示可放置物品UI,切换物品
  • 等等。。。

源码

源码整理好了我会放上来,催更加急

参考

【视频】https://www.youtube.com/watch?v=rKp9fWvmIww&t=567s

完结

赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注,以便我第一时间收到反馈,你的每一次支持都是我不断创作的最大动力。当然如果你发现了文章中存在错误或者有更好的解决方法,也欢迎评论私信告诉我哦!

好了,我是向宇,https://xiangyu.blog.csdn.net

一位在小公司默默奋斗的开发者,出于兴趣爱好,于是最近才开始自习unity。如果你遇到任何问题,也欢迎你评论私信找我, 虽然有些问题我可能也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~
在这里插入图片描述

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

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

相关文章

2024年天津天狮学院食品质量与安全专业《普通化学》考试大纲

2024年天津天狮学院食品质量与安全专业高职升本入学考试《普通化学》考试大纲 一、考试性质 《普通化学》专业课程考试是天津天狮学院食品质量与安全专业高职升本入学考试 的必考科目之一&#xff0c;其性质是考核学生是否达到了升入本科继续学习的要求而进行的选拔性考试。《…

[计算机网络]运输层概述

虽然我自己也不知道写在前面和前言有什么区别..... 这个系列其实是针对<深入浅出计算机网络>的简单总结,加入了一点个人的理解和浅薄见识,如果您有一些更好的意见和见解,欢迎随时协助我改正,感激不尽啦. 最近心态平和了不少, 和过去也完全做了个割舍吧,既然痛苦和压力的…

记录华为云服务器(Linux 可视化 宝塔面板)-- 安全组篇

文章目录 前言安全组说明安全组的特性安全组的应用场景 进入安全组添加基本规则添加自定义规则如有启发&#xff0c;可点赞收藏哟~ 前言 和windows防火墙类似&#xff0c;安全组是一种虚拟防火墙&#xff0c;具备状态检测和数据包过滤功能&#xff0c;可以对进出云服务器的流量…

typeof,instanceof

1.typeof typeof运算符返回的结果是以小写的字符串表示的变量的类型 2.instanceof instanceof运算符用于判断右边构造函数的原型对象是否在左边对象的原型链上 let arr[]let obj{}let datenew Dateconsole.log(arr instanceof Array)console.log(arr instanceof Object)conso…

Maven 简单配置阿里云镜像

配置步骤&#xff1a; 1、找到 maven 的安装目录&#xff0c;修改settings.xml 2、在文件中找到<mirrors>标签&#xff0c;然后再标签中添加阿里云配置即可 <mirror><id>aliyunmaven</id><mirrorOf>*</mirrorOf><name>阿里云公共…

巧妙之中见真章:深入解析常用的创建型设计模式

设计模式之创建型设计模式详解 一、设计模式是什么&#xff1f;二、模板方法2.1、代码结构2.2、符合的设计原则2.3、如何扩展代码2.4、小结 三、观察者模式3.1、代码结构3.2、符合的设计原则3.3、如何扩展代码3.4、小结 四、策略模式4.1、代码结构4.2、符合的设计原则4.3、如何…

Pinctrl子系统和GPIO子系统

Pinctrl子系统&#xff1a; 借助Princtr子系统来设置一个Pin的复用和电气属性&#xff1b; pinctrl子系统主要做的工作是&#xff1a;1. 获取设备树中的PIN信息&#xff1b;2.根据获取到的pin信息来设置的Pin的复用功能&#xff1b;3.根据获取到的pin信息去设置pin的电气特性…

【机器学习 | 聚类】关于聚类最全评价方法大全,确定不收藏?

&#x1f935;‍♂️ 个人主页: AI_magician &#x1f4e1;主页地址&#xff1a; 作者简介&#xff1a;CSDN内容合伙人&#xff0c;全栈领域优质创作者。 &#x1f468;‍&#x1f4bb;景愿&#xff1a;旨在于能和更多的热爱计算机的伙伴一起成长&#xff01;&#xff01;&…

访谈 破风之人毛京波,选择难而正确的路

“无论是在燃油时代还是电动时代&#xff0c;我们所做的一切&#xff0c;只为回归纯粹的驾驶乐趣。”履新路特斯中国总裁整整一年的毛京波&#xff0c;从不放过任何一个展示路特斯品牌驾驭精神的机会。 11月17日&#xff0c;广州车展开幕首日&#xff0c;位于5.2馆的路特斯“冠…

【理解ARM架构】 散列文件 | 重定位

&#x1f431;作者&#xff1a;一只大喵咪1201 &#x1f431;专栏&#xff1a;《理解ARM架构》 &#x1f525;格言&#xff1a;你只管努力&#xff0c;剩下的交给时间&#xff01; 目录 &#x1f3d3;引出重定位&#x1f3d3;散列文件&#x1f3d3;可读可写数据段重定位&#…

OSG编程指南<十一>:OSG几何体操作及三维地形创建

1、简化几何体 在 OSG 中&#xff0c;场景都是由基本的绘图基元构成的&#xff0c;基本的绘图基元构成简单的几何体&#xff0c;简单的几何体构成复杂的几何体&#xff0c;复杂的几何体最终构造成复杂的场景。当多个几何体组合时&#xff0c;可能 存在多种降低场景渲染效率的原…

JAVA小游戏“简易版王者荣耀”

第一步是创建项目 项目名自拟 第二部创建个包名 来规范class 然后是创建类 GameFrame 运行类 package com.sxt;import java.awt.Graphics; import java.awt.Image; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; im…

Scope 模块

Scope 模块可以连接任何类型的实数信号线 (不支持复数)。 波形显示界面主要包括两个部分: Scope 独有的工具栏、波形显示区域。 波形显示界面默认是黑色背景, 当有单个信号输入时, 信号线是黄色的。 Scope 模块也有菜单栏, 只不过默认将其句柄和显示都隐藏起来, 可以通过下面…

5 个适用于 Windows 的顶级免费数据恢复软件

对于计算机来说&#xff0c;最重要的是用户数据。除了您的数据之外&#xff0c;有关计算机的其他所有内容都是可替换的。这三个是数据丢失的最常见原因&#xff1a; 文件/文件夹删除丢失分区分区损坏 文件/文件夹删除 文件/文件夹删除是最常见的数据丢失类型。大多数时候&am…

《微信小程序开发从入门到实战》学习三十一

3.4 开发参与投票页面 3.4.9 显示投票结果 在实际使用中&#xff0c;一个用户不能对同一个投票进行重复提交&#xff0c;因此需要向服务器端提交投票结果和提交用户ID。另外页面&#xff0c;需要完善。用户提交完投票后 &#xff0c;还需要显示投票目前的结果&#xff0c;提交…

C语言进阶之笔试题详解(1)

引言&#xff1a; 对指针知识进行简单的回顾&#xff0c;然后再完成笔试题。 ✨ 猪巴戒&#xff1a;个人主页✨ 所属专栏&#xff1a;《C语言进阶》 &#x1f388;跟着猪巴戒&#xff0c;一起学习C语言&#x1f388; 目录 引言&#xff1a; 知识简单回顾 指针是什么 指针变…

1 时间序列模型入门: LSTM

0 前言 循环神经网络&#xff08;Recurrent Neural Network&#xff0c;RNN&#xff09;是一种用于处理序列数据的神经网络。相比一般的神经网络来说&#xff0c;他能够处理序列变化的数据。比如某个单词的意思会因为上文提到的内容不同而有不同的含义&#xff0c;RNN就能够很好…

2023-3年CSDN创作纪念日

机缘 今天开开心心出门去上班&#xff0c;就收到了一个csdn私信&#xff0c;打开一看说是给我惊喜来着&#xff0c;我心想csdn还能给惊喜&#xff1f;以为是有什么奖品或者周边之类的&#xff0c;结果什么也没有&#xff0c;打开就是一份信&#x1f602;。 也挺不错的&#xf…