Unity 自定义房间布局系统 设计与实现一个灵活的房间放置系统 ——自定义房间区域功能

本文主要是介绍Unity 自定义房间布局系统 设计与实现一个灵活的房间放置系统 ——自定义房间区域功能,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

自定义房间区域功能

效果:

请添加图片描述
请添加图片描述

功能:

  • 能够自定义房间的大小
  • 一键生成放置区域
  • 可控的放置网格点
  • 当物体放置到区域内可自动吸附
  • 物体是否可放置,放置时如果与其他物体交叉则不可放置(纯算法计算)
  • 管理房间内的物体,能够添加或删除房间内的物体
  • 直观可调整的视觉效果

核心功能——RoomReferenceFrame

管理房间的边界信息和相关操作:

1.1 属性和字段

这些字段定义了房间的显示属性和调整手柄的参数,比如网格颜色、手柄大小和网格间隔等。
在这里插入图片描述

public bool showWorldArea = true;
public bool showForbidArea = true;
public Color gizmosColor = Color.black;
public Color gizmosXColor = new Color(1 , 0 , 0 , 0.2f);
public Color gizmosYColor = new Color(0 , 1 , 0 , 0.2f);
public Color gizmosZColor = new Color(0 , 0 , 1 , 0.2f);
public float HandlesSize = 0.5f;
public Vector2Int LeftAndRightInterval = Vector2Int.one*5;
public Vector2Int TopAndBottomInterval = Vector2Int.one*5;
public Vector2Int FrontAndBackInterval = Vector2Int.one*5;
1.2 初始位置定义
Vector3 worldLeftLocation, worldRightLocation, worldTopLocation, worldBottomLocation, worldFrontLocation, worldBackLocation;
[HideInInspector]
public Vector3 leftLocation = new Vector3(-5 , 0 , 0);
[HideInInspector]
public Vector3 rightLocation = new Vector3(5 , 0 , 0);
[HideInInspector]
public Vector3 topLocation = new Vector3(0 , 5 , 0);
[HideInInspector]
public Vector3 bottomLocation = new Vector3(0 , -5 , 0);
[HideInInspector]
public Vector3 frontLocation = new Vector3(0 , 0 , 5);
[HideInInspector]
public Vector3 backLocation = new Vector3(0 , 0 , -5);
public int CornerContagion = 1;

这些字段定义了房间各个边界的位置,使用HideInInspector属性隐藏在Inspector中,不直接显示给用户。

1.3 网格对齐方法
public Vector3 SnapToGrid(Vector3 point , SnapDirection snapDirection)
{Vector3 localPoint = transform.InverseTransformPoint(point);Vector3 localStart;Vector3 localEnd;int totalIntervals;switch(snapDirection){case SnapDirection.Front:case SnapDirection.Back:localStart=transform.InverseTransformPoint(worldLeftLocation);localEnd=transform.InverseTransformPoint(worldRightLocation);totalIntervals=FrontAndBackInterval.x-1;localPoint.x=SnapCoordinate(localPoint.x , localStart.x , localEnd.x , totalIntervals);localStart=transform.InverseTransformPoint(worldBottomLocation);localEnd=transform.InverseTransformPoint(worldTopLocation);totalIntervals=FrontAndBackInterval.y-1;localPoint.y=SnapCoordinate(localPoint.y , localStart.y , localEnd.y , totalIntervals);// Z轴坐标保持不变break;case SnapDirection.Left:case SnapDirection.Right:localStart=transform.InverseTransformPoint(worldFrontLocation);localEnd=transform.InverseTransformPoint(worldBackLocation);totalIntervals=LeftAndRightInterval.x-1;localPoint.z=SnapCoordinate(localPoint.z , localStart.z , localEnd.z , totalIntervals);// 与Front/Back情况相同的Y轴处理localStart=transform.InverseTransformPoint(worldBottomLocation);localEnd=transform.InverseTransformPoint(worldTopLocation);totalIntervals=LeftAndRightInterval.y-1;localPoint.y=SnapCoordinate(localPoint.y , localStart.y , localEnd.y , totalIntervals);// X轴坐标保持不变break;case SnapDirection.Top:case SnapDirection.Bottom:localStart=transform.InverseTransformPoint(worldLeftLocation);localEnd=transform.InverseTransformPoint(worldRightLocation);totalIntervals=TopAndBottomInterval.x-1;localPoint.x=SnapCoordinate(localPoint.x , localStart.x , localEnd.x , totalIntervals);localStart=transform.InverseTransformPoint(worldFrontLocation);localEnd=transform.InverseTransformPoint(worldBackLocation);totalIntervals=TopAndBottomInterval.y-1;localPoint.z=SnapCoordinate(localPoint.z , localStart.z , localEnd.z , totalIntervals);// Y轴坐标保持不变break;}return transform.TransformPoint(localPoint);
}private float SnapCoordinate(float coordinate , float start , float end , int intervals)
{float relativePos = (coordinate-start)/(end-start);int intervalIndex = Mathf.RoundToInt(relativePos*intervals);if(intervalIndex<CornerContagion)intervalIndex=CornerContagion;if(intervalIndex>intervals-(CornerContagion))intervalIndex=intervals-(CornerContagion);float lerpValue = (float)intervalIndex/intervals;return Mathf.Lerp(start , end , lerpValue);
}
SnapToGrid方法用于将一个点对齐到网格节点上,使得物体在特定的方向上沿网格对齐。这在房间布局和物体摆放中非常有用,有助于保持场景中的物体排列整齐。

请添加图片描述

1.坐标转换:将输入点point转换到局部坐标系localPoint。
2.根据对齐方向处理:

  • Front/Back方向:对局部x和y坐标进行对齐。
  • Left/Right方向:对局部z和y坐标进行对齐。
  • Top/Bottom方向:对局部x和z坐标进行对齐。

3.调用SnapCoordinate方法:计算并返回对齐后的坐标。
4.坐标还原:将对齐后的局部坐标转换回世界坐标。

SnapCoordinate方法用于将单个坐标值对齐到最近的网格节点,具体步骤如下:

1.计算相对位置:将坐标coordinate标准化到0到1范围内。
2.确定区间索引:根据相对位置和总区间数计算所在区间索引。
3.调整区间索引:确保区间索引不超出范围,避免靠近边界的物体超出区域。
4.计算对齐坐标:使用线性插值计算最终的对齐坐标。

优点
  • 可维护性强:将对齐逻辑封装在SnapToGrid和SnapCoordinate方法中,便于代 码的维护和扩展。
  • 灵活性高:通过SnapDirection参数指定对齐方向,适应不同场景需求。
  • 防止超出边界:CornerContagion参数控制边界区域,确保对齐后的坐标不会超出预设范围。
  • 简化复杂计算:使用线性插值和标准化简化坐标计算,保证精度和效率。
    这些好处和技巧使得该方法在实现房间物体的网格对齐时既简洁高效,又具备高度的灵活性和可控性。
1.4 其他辅助方法

ResetArea(): 重置房间边界位置。
请添加图片描述

public void ResetArea()
{leftLocation=new Vector3(-1 , 0 , 0);rightLocation=new Vector3(1 , 0 , 0);topLocation=new Vector3(0 , 1 , 0);bottomLocation=new Vector3(0 , -1 , 0);frontLocation=new Vector3(0 , 0 , 1);backLocation=new Vector3(0 , 0 , -1);transform.rotation=Quaternion.identity;
}

AddRoomItem(MultiMeshAreaCalculator roomItem): 添加房间内的物体。

List<MultiMeshAreaCalculator> RoomItems = new List<MultiMeshAreaCalculator>();
List<AreaData> AreaDatas = new List<AreaData>();//AreaData存储空间数据,一般为八个Vector3数据,一个方块的8个点位public void AddRoomItem(MultiMeshAreaCalculator roomItem)
{RoomItems.Add(roomItem);AddItemData(roomItem);
}
void AddItemData(MultiMeshAreaCalculator roomItem)
{AreaDatas.AddRange(roomItem.GetItemData());//roomItem.GetItemData()提供物体占用区域数据
}

RemoveAddRoomItem(MultiMeshAreaCalculator roomItem): 删除房间内的物体。

public void RemoveAddRoomItem(MultiMeshAreaCalculator roomItem)
{RoomItems.Remove(roomItem);AreaDatas.Clear();foreach(var item in RoomItems){AddItemData(item);}
}

AutoAdjustEdge 方法用于自动调整房间边界的位置。这是通过在每个方向上投射射线(Ray)并检测与碰撞体的交点来实现的。
请添加图片描述

public void AutoAdjustEdge()
{RaycastHit hit;Ray ray = new Ray(transform.position , transform.up);if (Physics.Raycast(ray , out hit))topLocation = transform.InverseTransformPoint(hit.point);ray = new Ray(transform.position , -transform.up);if (Physics.Raycast(ray , out hit))bottomLocation = transform.InverseTransformPoint(hit.point);ray = new Ray(transform.position , transform.right);if (Physics.Raycast(ray , out hit))rightLocation = transform.InverseTransformPoint(hit.point);ray = new Ray(transform.position , -transform.right);if (Physics.Raycast(ray , out hit))leftLocation = transform.InverseTransformPoint(hit.point);ray = new Ray(transform.position , transform.forward);if (Physics.Raycast(ray , out hit))frontLocation = transform.InverseTransformPoint(hit.point);ray = new Ray(transform.position , -transform.forward);if (Physics.Raycast(ray , out hit))backLocation = transform.InverseTransformPoint(hit.point);
}
射线投射 (Raycasting)
  • 使用 Physics.Raycast 方法在各个方向上投射射线,检测与其他物体的碰撞。
  • 通过射线投射,可以精确地确定房间边界的实际位置,而不必手动调整。
坐标转换
topLocation = transform.InverseTransformPoint(hit.point);
  • 使用 transform.InverseTransformPoint 方法将世界坐标系下的碰撞点转换为本地坐标系。
  • 这样做可以确保边界位置与房间物体的本地坐标系一致,便于后续的计算和调整。
自适应性
  • 通过自动调整边界位置,AutoAdjustEdge 方法使得房间能够自适应环境中的其他物体,自动调整其尺寸和位置。
  • 这种自适应性对于动态环境中的房间管理和布局调整非常有用。

准备好!上硬菜🥦

IsOverlapping 方法用于检查一个 roomItem 是否与现有房间的任意部分重叠,并返回重叠的 Renderer 列表。

public bool IsOverlapping(MultiMeshAreaCalculator roomItem , out List<Renderer> renderers)
{bool returnValue = true;List<AreaData> Temp = new List<AreaData>();renderers=new List<Renderer>();if(roomItem.ListOverallBounds.Count!=0){Temp.AddRange(roomItem.ListOverallBounds);}if(roomItem.childData!=null){foreach(var item in roomItem.childData){if(item.ListOverallBounds.Count!=0){Temp.AddRange(item.ListOverallBounds);}}}foreach(var item in Temp){foreach(var AreaDate in AreaDatas){if(AreCubesOverlapping(item.GetCorners() , AreaDate.GetCorners())){renderers.Add(item.renderer);returnValue=false;}}}return returnValue;
}

判断房间物体是否重叠——分离轴定理(Separating Axis Theorem, SAT)

这里不详细解释SAT的原理,我们只学怎么利用这个算法

AreCubesOverlapping 方法

这个方法运用了多种技术和算法,特别是基于分离轴定理(Separating Axis Theorem, SAT)的碰撞检测算法。
这些方法共同作用,管理房间物体和边界的逻辑。

  1. 获取所有可能的分离轴
    从 corners1 和 corners2 中获取本地轴。
    通过两组顶点的边计算交叉乘积,得到法向量。
  2. 检查每个轴上的分离
    对每个轴调用 OverlapOnAxis 方法,检查两个立方体在该轴上的投影是否重叠。
bool AreCubesOverlapping(Vector3[] corners1 , Vector3[] corners2)
{// 获取所有可能的分离轴List<Vector3> axes = new List<Vector3>();// 添加立方体1的本地轴axes.AddRange(GetLocalAxes(corners1));// 添加立方体2的本地轴axes.AddRange(GetLocalAxes(corners2));// 对每一对边计算交叉乘积,得到立方体之间边的法向量for(int i = 0; i<corners1.Length; i+=4){for(int j = 0; j<corners2.Length; j+=4){Vector3 edge1 = corners1[i+1]-corners1[i];Vector3 edge2 = corners2[j+1]-corners2[j];Vector3 axis = Vector3.Cross(edge1 , edge2).normalized;if(axis!=Vector3.zero){axes.Add(axis);}}}// 检查每一个轴,查看是否在该轴上存在分离foreach(Vector3 axis in axes){if(!OverlapOnAxis(corners1 , corners2 , axis)){return false; // 在这个轴上找到分离,说明不相交}}return true; // 所有可能的分离轴都没有找到分离,说明相交
}
bool OverlapOnAxis(Vector3[] vertices1 , Vector3[] vertices2 , Vector3 axis)
{float min1 = float.MaxValue;float max1 = float.MinValue;float min2 = float.MaxValue;float max2 = float.MinValue;// 计算顶点在立方体1坐标轴上的投影foreach(Vector3 vertex in vertices1){float projection = Vector3.Dot(vertex , axis);min1=Mathf.Min(min1 , projection);max1=Mathf.Max(max1 , projection);}// 计算顶点在立方体2坐标轴上的投影foreach(Vector3 vertex in vertices2){float projection = Vector3.Dot(vertex , axis);min2=Mathf.Min(min2 , projection);max2=Mathf.Max(max2 , projection);}// 检查投影是否重叠if(max1<min2||max2<min1){return false; // 在这个轴上没有重叠}return true; // 重叠存在于这个轴上
}
GetLocalAxes 方法

获取立方体的本地轴。

List<Vector3> GetLocalAxes(Vector3[] corners)
{List<Vector3> axes = new List<Vector3>();Vector3 edge1 = corners[1] - corners[0];Vector3 edge2 = corners[2] - corners[0];Vector3 edge3 = corners[4] - corners[0];if (edge1 != Vector3.zero) axes.Add(edge1.normalized);if (edge2 != Vector3.zero) axes.Add(edge2.normalized);if (edge3 != Vector3.zero) axes.Add(edge3.normalized);return axes;
}

核心技术分离轴定理 (SAT):

请添加图片描述

  • 利用 SAT 检测两个凸多边形(这里是立方体)的碰撞。通过检查投影在所有可能的分离轴上是否重叠来判断碰撞。
  • 这种方法非常高效,可以处理复杂的三维碰撞检测。

局部轴计算:

  • GetLocalAxes 方法提取了立方体的本地轴,这些轴是分离轴的一部分。
  • 通过计算边之间的交叉乘积,生成了额外的分离轴,提高了检测的准确性。

投影计算:

  • OverlapOnAxis 方法通过将立方体的顶点投影到分离轴上,计算最小和最大投影值。
  • 检查投影是否重叠,决定了两个立方体在该轴上是否分离。

代码封装与复用:

  • 通过将复杂的碰撞检测逻辑分解为多个方法(如 AreCubesOverlapping、GetLocalAxes 和 OverlapOnAxis),提高了代码的可读性和可维护性。
  • 这种封装方式使得每个方法的职责单一,便于调试和扩展。
这些技术亮点和技巧使得 IsOverlapping 方法能够高效、准确地检测房间物体之间的重叠情况,对于复杂的房间布局和物体摆放场景具有重要意义。

视觉效果

以下代码主要用于在Unity编辑器中通过Gizmos绘制房间区域和禁区区域,以便开发者在编辑模式下直观地看到这些区域。具体代码包含OnDrawGizmos、OnDrawGizmosSelected、DrawChildArea、DrawMesh和DrawInterval等方法。
在这里插入图片描述

OnDrawGizmos
  • 当在编辑器中绘制Gizmos时调用。
  • showWorldArea:是否一直显示在Scene视图,不选中也会显示网格
  • isSelect:为了避免OnDrawGizmos与OnDrawGizmosSelected渲染两次网格
  • showForbidArea:显示禁区区域,在编辑器模式下更加直观的观察物体的放置区域
bool isSelect = false;private void OnDrawGizmos()
{if (showWorldArea && !isSelect)DrawMesh();isSelect = false;if (showForbidArea){foreach (var item in AreaDatas){DrawChildArea(item.GetCorners());}}
}
OnDrawGizmosSelected
  • 当在编辑器中选择对象时调用。
  • isSelect:为了避免OnDrawGizmos与OnDrawGizmosSelected渲染两次网格
  • DrawMesh:只要选中就一定会渲染网格。
  • 绘制左右、上下、前后边界线,后面使用手柄作为连接。
private void OnDrawGizmosSelected()
{isSelect = true;DrawMesh();Gizmos.color = gizmosColor;Gizmos.DrawLine(worldLeftLocation, worldRightLocation);Gizmos.DrawLine(worldTopLocation, worldBottomLocation);Gizmos.DrawLine(worldFrontLocation, worldBackLocation);
}
DrawChildArea
  • 绘制禁止区域的立方体。
  • 依次绘制立方体的前面、后面以及连接前后面的线。
void DrawChildArea(Vector3[] corners)
{if (corners.Length != 8)return;Gizmos.color = gizmosColor;Gizmos.DrawLine(corners[0], corners[1]);Gizmos.DrawLine(corners[1], corners[3]);Gizmos.DrawLine(corners[3], corners[2]);Gizmos.DrawLine(corners[2], corners[0]);Gizmos.DrawLine(corners[4], corners[5]);Gizmos.DrawLine(corners[5], corners[7]);Gizmos.DrawLine(corners[7], corners[6]);Gizmos.DrawLine(corners[6], corners[4]);Gizmos.DrawLine(corners[0], corners[4]);Gizmos.DrawLine(corners[1], corners[5]);Gizmos.DrawLine(corners[2], corners[6]);Gizmos.DrawLine(corners[3], corners[7]);
}
DrawMesh
  • 绘制网格
  • 调用DrawInterval方法绘制网格的各个面。
void DrawMesh()
{Gizmos.color = gizmosColor;worldLeftLocation = transform.TransformPoint(leftLocation);worldRightLocation = transform.TransformPoint(rightLocation);worldTopLocation = transform.TransformPoint(topLocation);worldBottomLocation = transform.TransformPoint(bottomLocation);worldFrontLocation = transform.TransformPoint(frontLocation);worldBackLocation = transform.TransformPoint(backLocation);DrawInterval(FindOppositePoint(worldLeftLocation, transform.position, worldBottomLocation),FindOppositePoint(worldRightLocation, transform.position, worldBottomLocation),TopAndBottomInterval.x, worldFrontLocation, worldBackLocation, gizmosYColor);DrawInterval(FindOppositePoint(worldFrontLocation, transform.position, worldBottomLocation),FindOppositePoint(worldBackLocation, transform.position, worldBottomLocation),TopAndBottomInterval.y, worldLeftLocation, worldRightLocation, gizmosYColor);DrawInterval(FindOppositePoint(worldTopLocation, transform.position, worldRightLocation),FindOppositePoint(worldBottomLocation, transform.position, worldRightLocation),LeftAndRightInterval.y, worldFrontLocation, worldBackLocation, gizmosXColor);DrawInterval(FindOppositePoint(worldFrontLocation, transform.position, worldRightLocation),FindOppositePoint(worldBackLocation, transform.position, worldRightLocation),LeftAndRightInterval.x, worldTopLocation, worldBottomLocation, gizmosXColor);DrawInterval(FindOppositePoint(worldLeftLocation, transform.position, worldFrontLocation),FindOppositePoint(worldRightLocation, transform.position, worldFrontLocation),FrontAndBackInterval.x, worldTopLocation, worldBottomLocation, gizmosZColor);DrawInterval(FindOppositePoint(worldTopLocation, transform.position, worldFrontLocation),FindOppositePoint(worldBottomLocation, transform.position, worldFrontLocation),FrontAndBackInterval.y, worldLeftLocation, worldRightLocation, gizmosZColor);
}
void DrawInterval(Vector3 A, Vector3 B, int interval, Vector3 positiveDirection, Vector3 negativeDirection, Color gizmosColor)
{float a = gizmosColor.a;Color ProhibitedAreaColor = Color.black;ProhibitedAreaColor.a = a * 0.2f;Gizmos.color = gizmosColor;interval--;for (int i = 0; i <= interval; i++){Gizmos.color = i < CornerContagion || i > interval - CornerContagion ? ProhibitedAreaColor : gizmosColor;if (i == 0){Gizmos.DrawLine(A, FindOppositePoint(A, transform.position, positiveDirection));Gizmos.DrawLine(A, FindOppositePoint(A, transform.position, negativeDirection));}else if (i == interval){Gizmos.DrawLine(B, FindOppositePoint(B, transform.position, positiveDirection));Gizmos.DrawLine(B, FindOppositePoint(B, transform.position, negativeDirection));}else{Vector3 target = Vector3.Lerp(A, B, (float)i / (float)interval);Gizmos.DrawLine(target, FindOppositePoint(target, transform.position, positiveDirection));Gizmos.DrawLine(target, FindOppositePoint(target, transform.position, negativeDirection));}}
}
Vector3 FindOppositePoint(Vector3 a, Vector3 b, Vector3 c)
{return a + c - b;
}

Gizmos绘制

  • 使用OnDrawGizmos和OnDrawGizmosSelected方法在Unity编辑器中绘制Gizmos,便于在编辑器中直观地查看对象及其区域。
  • 通过Gizmos可以在编辑器模式下提供视觉反馈,而不会影响运行时性能。

动态计算

  • 使用Vector3.Lerp方法在两个点之间插值计算,可以动态绘制复杂的网格结构。
  • 通过FindOppositePoint方法计算对立点,使得代码简洁高效。

参数控制

  • CornerContagion参数用于控制角落的禁区区域,使得禁区区域的绘制更加灵活。
这些代码通过利用Gizmos在Unity编辑器中绘制出房间的边界和禁区区域,为开发者提供了直观的视觉反馈,方便房间的布局和调整。通过射线投射、颜色区分和动态计算等技术,使得代码具备了很高的可维护性和可读性。

编辑器扩展——Editor_RoomReferenceFrame

用于在Unity编辑器中自定义房间边界的可视化和交互操作。

手动调整区域

该方法负责在Scene视图中绘制控制点和拖拽操作,允许用户通过控制点调整房间边界的位置。具体实现包括:

  • 使用Handles类绘制控制点(手柄)并允许用户拖拽。
  • 转换控制点的世界坐标和本地坐标。
  • 更新RoomReferenceFrame对象的边界位置。在这里插入图片描述
    (一个圆锥手柄不太好看,又增加一个方片手柄,嗯~舒服多了)
 private void OnSceneGUI(){RoomReferenceFrame roomRefFrame = (RoomReferenceFrame)target;Transform trans = roomRefFrame.transform;// 使用 Transform.TransformDirection 将方向向量转换为对象的本地空间Vector3 localLeftDirection = trans.TransformDirection(Vector3.left);Vector3 localRightDirection = trans.TransformDirection(Vector3.right);Vector3 localUpDirection = trans.TransformDirection(Vector3.up);Vector3 localDownDirection = trans.TransformDirection(Vector3.down);Vector3 localForwardDirection = trans.TransformDirection(Vector3.forward);Vector3 localBackDirection = trans.TransformDirection(Vector3.back);// 设置控制点的颜色Color color = roomRefFrame.gizmosColor;color.a=0.7f;Handles.color=color;// 在特定轴上绘制并拖拽控制点Handles.CapFunction AcapFunction = Handles.ConeHandleCap;//SphereHandleCap//CubeHandleCap//ConeHandleCapHandles.CapFunction BcapFunction = Handles.RectangleHandleCap;//CircleHandleCap//RectangleHandleCapVector3 L_SliderOffset = Vector3.Normalize(roomRefFrame.leftLocation - roomRefFrame.transform.position) * ( roomRefFrame.HandlesSize / 2 );Vector3 R_SliderOffset = Vector3.Normalize(roomRefFrame.rightLocation - roomRefFrame.transform.position) * ( roomRefFrame.HandlesSize / 2 );Vector3 T_SliderOffset = Vector3.Normalize(roomRefFrame.topLocation - roomRefFrame.transform.position) * ( roomRefFrame.HandlesSize / 2 );Vector3 B_SliderOffset = Vector3.Normalize(roomRefFrame.bottomLocation - roomRefFrame.transform.position) * ( roomRefFrame.HandlesSize / 2 );Vector3 F_SliderOffset = Vector3.Normalize(roomRefFrame.frontLocation - roomRefFrame.transform.position) * ( roomRefFrame.HandlesSize / 2 );Vector3 E_SliderOffset = Vector3.Normalize(roomRefFrame.backLocation - roomRefFrame.transform.position) * ( roomRefFrame.HandlesSize / 2 );roomRefFrame.leftLocation=trans.InverseTransformPoint(Handles.Slider(trans.TransformPoint(roomRefFrame.leftLocation+ L_SliderOffset) , localLeftDirection , roomRefFrame.HandlesSize , AcapFunction , 1f)) - L_SliderOffset;roomRefFrame.rightLocation=trans.InverseTransformPoint(Handles.Slider(trans.TransformPoint(roomRefFrame.rightLocation+ R_SliderOffset) , localRightDirection , roomRefFrame.HandlesSize , AcapFunction , 1f))- R_SliderOffset;roomRefFrame.topLocation=trans.InverseTransformPoint(Handles.Slider(trans.TransformPoint(roomRefFrame.topLocation+ T_SliderOffset) , localUpDirection , roomRefFrame.HandlesSize , AcapFunction , 1f))- T_SliderOffset;roomRefFrame.bottomLocation=trans.InverseTransformPoint(Handles.Slider(trans.TransformPoint(roomRefFrame.bottomLocation+ B_SliderOffset) , localDownDirection , roomRefFrame.HandlesSize , AcapFunction , 1f))- B_SliderOffset;roomRefFrame.frontLocation=trans.InverseTransformPoint(Handles.Slider(trans.TransformPoint(roomRefFrame.frontLocation + F_SliderOffset) , localForwardDirection , roomRefFrame.HandlesSize , AcapFunction , 1f)) - F_SliderOffset;roomRefFrame.backLocation=trans.InverseTransformPoint(Handles.Slider(trans.TransformPoint(roomRefFrame.backLocation + E_SliderOffset) , localBackDirection , roomRefFrame.HandlesSize , AcapFunction , 1f)) - E_SliderOffset;// 设置控制点的颜色Handles.color=roomRefFrame.gizmosColor;roomRefFrame.leftLocation=trans.InverseTransformPoint(Handles.Slider(trans.TransformPoint(roomRefFrame.leftLocation) , localLeftDirection , roomRefFrame.HandlesSize , BcapFunction , 1f));roomRefFrame.rightLocation=trans.InverseTransformPoint(Handles.Slider(trans.TransformPoint(roomRefFrame.rightLocation) , localRightDirection , roomRefFrame.HandlesSize , BcapFunction , 1f));roomRefFrame.topLocation=trans.InverseTransformPoint(Handles.Slider(trans.TransformPoint(roomRefFrame.topLocation) , localUpDirection , roomRefFrame.HandlesSize , BcapFunction , 1f));roomRefFrame.bottomLocation=trans.InverseTransformPoint(Handles.Slider(trans.TransformPoint(roomRefFrame.bottomLocation) , localDownDirection , roomRefFrame.HandlesSize , BcapFunction , 1f));roomRefFrame.frontLocation=trans.InverseTransformPoint(Handles.Slider(trans.TransformPoint(roomRefFrame.frontLocation) , localForwardDirection , roomRefFrame.HandlesSize , BcapFunction , 1f));roomRefFrame.backLocation=trans.InverseTransformPoint(Handles.Slider(trans.TransformPoint(roomRefFrame.backLocation) , localBackDirection , roomRefFrame.HandlesSize , BcapFunction , 1f));// 如果位置发生变化,标记场景已经修改if (GUI.changed){Undo.RecordObject(roomRefFrame , "Move control points");EditorUtility.SetDirty(roomRefFrame);}}

编辑器面板美化🌸

该方法负责在Inspector面板中绘制自定义的GUI,允许用户通过按钮和滑块调整房间属性。包括:

  • “一键调整区域”按钮用于一键调整区域和重置区域。
  • 调整手柄大小和颜色等。在这里插入图片描述
bool ChangeControl = false;public override void OnInspectorGUI()
{//base.OnInspectorGUI();RoomReferenceFrame roomRefFrame = (RoomReferenceFrame)target;GUILayout.Space(5);if (GUILayout.Button("一键调整区域" , GUILayout.Height(50))){roomRefFrame.AutoAdjustEdge();}GUILayout.Space(15);EditorGUILayout.BeginVertical("HelpBox");if(ChangeControl){if(GUILayout.Button("隐藏" , "prebutton"))ChangeControl=!ChangeControl;}else{if(GUILayout.Button("调整视觉效果" , "prebutton"))ChangeControl=!ChangeControl;}if(ChangeControl){GUILayout.Space(10);GUILayout.BeginHorizontal();roomRefFrame.showWorldArea = GUILayout.Toggle(roomRefFrame.showWorldArea , new GUIContent("显示网格" , "不选中此物体时也会显示网格") , GUILayout.Width(100));roomRefFrame.showForbidArea = GUILayout.Toggle(roomRefFrame.showForbidArea , new GUIContent("显示禁区" , "显示禁止放置的区域,一般情况下放置物体后才会显示") , GUILayout.Width(100));GUILayout.EndHorizontal();GUILayout.Space(10);GUILayout.BeginHorizontal();GUILayout.Label("手柄调整" , GUILayout.Width(70));roomRefFrame.HandlesSize=EditorGUILayout.Slider(roomRefFrame.HandlesSize , 0.1f , 3 , GUILayout.Width(150));roomRefFrame.gizmosColor=EditorGUILayout.ColorField(roomRefFrame.gizmosColor , GUILayout.Width(70));GUILayout.EndHorizontal();GUILayout.Space(10);GUILayout.BeginHorizontal();GUILayout.Label("网格颜色" , GUILayout.Width(70));roomRefFrame.gizmosXColor=EditorGUILayout.ColorField(roomRefFrame.gizmosXColor , GUILayout.Width(70));roomRefFrame.gizmosYColor=EditorGUILayout.ColorField(roomRefFrame.gizmosYColor , GUILayout.Width(70));roomRefFrame.gizmosZColor=EditorGUILayout.ColorField(roomRefFrame.gizmosZColor , GUILayout.Width(70));GUILayout.EndHorizontal();}EditorGUILayout.EndVertical();GUILayout.Space(10);GUILayout.Label("网格间隔调整:" , GUILayout.Width(100));GUILayout.BeginHorizontal();GUILayout.Label("左右" , GUILayout.Width(40));roomRefFrame.LeftAndRightInterval=EditorGUILayout.Vector2IntField("" , roomRefFrame.LeftAndRightInterval);GUILayout.EndHorizontal();GUILayout.BeginHorizontal();GUILayout.Label("前后" , GUILayout.Width(40));roomRefFrame.FrontAndBackInterval=EditorGUILayout.Vector2IntField("" , roomRefFrame.FrontAndBackInterval);GUILayout.EndHorizontal();GUILayout.BeginHorizontal();GUILayout.Label("上下" , GUILayout.Width(40));roomRefFrame.TopAndBottomInterval=EditorGUILayout.Vector2IntField("" , roomRefFrame.TopAndBottomInterval);GUILayout.EndHorizontal();GUILayout.Space(10);GUILayout.BeginHorizontal();GUILayout.Label("边缘禁止系数" , GUILayout.Width(80));roomRefFrame.CornerContagion=EditorGUILayout.IntSlider(roomRefFrame.CornerContagion , 0 , Mathf.CeilToInt(roomRefFrame.FindMinimumInterval()/2)-1 , GUILayout.Width(200));GUILayout.EndHorizontal();GUILayout.Space(20);if(GUILayout.Button("重置")){if(EditorUtility.DisplayDialog("注意!" , "这将使放置区域恢复到开始状态。" , "是的" , "手滑了~")){roomRefFrame.ResetArea();}}if(GUI.changed){Undo.RecordObject(roomRefFrame , "Move control points");EditorUtility.SetDirty(roomRefFrame);}
}
通过了解RoomReferenceFrame和Editor_RoomReferenceFrame,我们应该理解自定义房间系统的实现逻辑和操作方法。前者负责定义和管理房间的边界信息和操作方法,后者则提供了在Unity编辑器中交互和可视化的支持。通过这两个脚本的配合,用户可以方便地在Unity中自定义和调整房间的边界和显示属性。

嚯!~ 到底了!

量太大了?

没事哥们,慢慢消化

这不,Demo来了~

这篇关于Unity 自定义房间布局系统 设计与实现一个灵活的房间放置系统 ——自定义房间区域功能的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1018561

相关文章

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

基于人工智能的图像分类系统

目录 引言项目背景环境准备 硬件要求软件安装与配置系统设计 系统架构关键技术代码示例 数据预处理模型训练模型预测应用场景结论 1. 引言 图像分类是计算机视觉中的一个重要任务,目标是自动识别图像中的对象类别。通过卷积神经网络(CNN)等深度学习技术,我们可以构建高效的图像分类系统,广泛应用于自动驾驶、医疗影像诊断、监控分析等领域。本文将介绍如何构建一个基于人工智能的图像分类系统,包括环境

水位雨量在线监测系统概述及应用介绍

在当今社会,随着科技的飞速发展,各种智能监测系统已成为保障公共安全、促进资源管理和环境保护的重要工具。其中,水位雨量在线监测系统作为自然灾害预警、水资源管理及水利工程运行的关键技术,其重要性不言而喻。 一、水位雨量在线监测系统的基本原理 水位雨量在线监测系统主要由数据采集单元、数据传输网络、数据处理中心及用户终端四大部分构成,形成了一个完整的闭环系统。 数据采集单元:这是系统的“眼睛”,

无人叉车3d激光slam多房间建图定位异常处理方案-墙体画线地图切分方案

墙体画线地图切分方案 针对问题:墙体两侧特征混淆误匹配,导致建图和定位偏差,表现为过门跳变、外月台走歪等 ·解决思路:预期的根治方案IGICP需要较长时间完成上线,先使用切分地图的工程化方案,即墙体两侧切分为不同地图,在某一侧只使用该侧地图进行定位 方案思路 切分原理:切分地图基于关键帧位置,而非点云。 理论基础:光照是直线的,一帧点云必定只能照射到墙的一侧,无法同时照到两侧实践考虑:关

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

高效+灵活,万博智云全球发布AWS无代理跨云容灾方案!

摘要 近日,万博智云推出了基于AWS的无代理跨云容灾解决方案,并与拉丁美洲,中东,亚洲的合作伙伴面向全球开展了联合发布。这一方案以AWS应用环境为基础,将HyperBDR平台的高效、灵活和成本效益优势与无代理功能相结合,为全球企业带来实现了更便捷、经济的数据保护。 一、全球联合发布 9月2日,万博智云CEO Michael Wong在线上平台发布AWS无代理跨云容灾解决方案的阐述视频,介绍了

嵌入式QT开发:构建高效智能的嵌入式系统

摘要: 本文深入探讨了嵌入式 QT 相关的各个方面。从 QT 框架的基础架构和核心概念出发,详细阐述了其在嵌入式环境中的优势与特点。文中分析了嵌入式 QT 的开发环境搭建过程,包括交叉编译工具链的配置等关键步骤。进一步探讨了嵌入式 QT 的界面设计与开发,涵盖了从基本控件的使用到复杂界面布局的构建。同时也深入研究了信号与槽机制在嵌入式系统中的应用,以及嵌入式 QT 与硬件设备的交互,包括输入输出设

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�