可以做黄金期权的网站,网页设计与制作课本,建设部网站注销一级建造师,十大旅游电子商务网站文章目录 最终效果前言绘制开始场景素材开始放置旋转物体扩展优化1. 绘制地图边界#xff0c;确保放置物品在指定区域内工作2. 让模型所占面积大小更加准确3. 隐藏白色瓦片指示区域 最终效果其他源码参考完结 最终效果 前言
其实3d物品建造装修系统之前就已经做过了#xff… 文章目录 最终效果前言绘制开始场景素材开始放置旋转物体扩展优化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.GetComponentGrid(); // 获取网格组件}//测试切换不同模型物品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.GetComponentPlaceableObject(); // 获取可放置物体组件obj.AddComponentObjectDrag(); // 添加拖拽组件}
}新增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.GetComponentBoxCollider();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.GetComponentObjectDrag();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?vrKp9fWvmIwwt567s
完结
赠人玫瑰手有余香如果文章内容对你有所帮助请不要吝啬你的点赞评论和关注以便我第一时间收到反馈你的每一次支持都是我不断创作的最大动力。当然如果你发现了文章中存在错误或者有更好的解决方法也欢迎评论私信告诉我哦
好了我是向宇https://xiangyu.blog.csdn.net
一位在小公司默默奋斗的开发者出于兴趣爱好于是最近才开始自习unity。如果你遇到任何问题也欢迎你评论私信找我 虽然有些问题我可能也不一定会但是我会查阅各方资料争取给出最好的建议希望可以帮助更多想学编程的人共勉~