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来了~

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

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

相关文章

云原生架构模式

本文主要介绍了云原生架构的主要设计模式&#xff0c;讨论了这些模式的优缺点及其适用场景&#xff0c;并探讨了在云计算环境中的应用和挑战。原文: Cloud-Native Architecture Patterns (Part 1)&#xff0c;Cloud-Native Architecture Patterns (Part 2) Bernard Hermant Uns…

【产品经理】总篇章

引言: 在最近频繁的产品职位面试中&#xff0c;我深刻体会到了作为产品需要的不仅仅是对市场和技术的敏锐洞察&#xff0c;更多的是在复杂多变的环境中&#xff0c;如何运用沟通、领导力和决策能力来引导产品从概念走向市场。这一系列博客将分享我多年经历和所学到的所以知识&a…

java —— 集合

一、集合的概念 集合可以看做是一个存储对象的容器&#xff0c;与数组不同的是集合可以存储不同类型的对象&#xff0c;但开发中一般不这样做。集合不能存储基本类型的对象&#xff0c;如果存储则需要将其转化为对应的包装类。 二、集合的分类 集合分为 Collection 和 Map 两…

React-表单受控绑定

概念&#xff1a;使用React组件的状态&#xff08;useState&#xff09;控制表单的状态 1.准备一个React状态值 2.通过value属性绑定状态&#xff0c;通过onChange属性绑定状态同步的函数

2024年5月总结及随笔之快乐五一

1. 回头看 日更坚持了517天。 读《天才与算法&#xff1a;人脑与AI的数学思维》更新完成 2023年至2024年5月底累计码字1177253字&#xff0c;累计日均码字2277字。 2024年5月码字95875字&#xff0c;同比增长66.7%&#xff0c;环比增长9.3%&#xff0c;日均码字数3092字&am…

STM32 定时器与PWM的LED控制

学习目标&#xff1a; 1. 使用定时器的某一个通道控制LED周期性亮灭&#xff1b; 2. 采用定时器PWM模式&#xff0c;让 LED 以呼吸灯方式渐亮渐灭。 一、定时器 1、STM32定时器介绍 STMicroelectronics是STM32微控制器中的重要块&#xff0c;具有丰富的外设和功能&#xff0…

Dijkstra求最短路篇一(全网最详细讲解两种方法,适合小白)(python,其他语言也适用)

前言&#xff1a; Dijkstra算法博客讲解分为两篇讲解&#xff0c;这两篇博客对所有有难点的问题都会讲解&#xff0c;小白也能很好理解。看完这两篇博客后保证收获满满。 本篇博客讲解朴素Dijkstra算法&#xff0c;第二篇博客讲解堆优化Dijkstra算法Dijkstra求最短路篇二(全网…

知识运维概述

文章目录 知识运维研究现状技术发展趋势 知识运维 由于构建全量的行业知识图谱成本很高&#xff0c;在真实的场景落地过程中&#xff0c;一般遵循小步快走、快速迭代的原则进行知识图谱的构建和逐步演化。知识运维是指在知识图谱初次构建完成之后&#xff0c;根据用户的使用反馈…

小白跟做江科大32单片机之对射式红外传感器计次

原理部分 1中断示意图&#xff0c;中断会打断主函数的执行&#xff0c;终端执行完成之后再返回主函数继续执行 2.STM32中断 这些灰色的是内核中断 这些白色的是普通中断 3.NVIC统一管理中断&#xff0c;每个中断通道都拥有16个可编程的优先等级&#xff0c;可对优先级进行分组…

Redis缓存(笔记一:缓存介绍和数据库启动)

目录 1、NoSQL数据库简介 2、Redis介绍 3、Redis(win系统、linux系统中操作) 3.1 win版本Redis启动 3.2 linux版本Redis启动 1、NoSQL数据库简介 技术的分类&#xff1a;&#xff08;发展史&#xff09; 1、解决功能性的问题&#xff1a;Java、Jsp、RDBMS、Tomcat、HTML、…

Filter和ServletContext和Listener

目录 Filter案例 解决全站乱码问题 登录权限校验 ServletContext对象 Listener&#xff08;监听器&#xff09; Filter案例 解决全站乱码问题 我们每次访问每个servlet都要书写处理请求和响应乱码的代码&#xff0c;这样代码十分冗余&#xff0c;所以我们可以在过滤中 We…

Java——变量

一、变量介绍 变量就是申请内存来存储值。也就是说&#xff0c;当创建变量的时候&#xff0c;需要在内存中申请空间。内存管理系统根据变量的类型为变量分配存储空间&#xff0c;分配的空间只能用来储存该类型数据。 1、变量声明和初始化 变量的声明&#xff1a; int a; i…

44-1 waf绕过 - WAF的分类

一、云 WAF 通常包含在 CDN 中的 WAF。在配置云 WAF 时&#xff0c;DNS 需要解析到 CDN 的 IP 上。请求 URL 时&#xff0c;数据包会先经过云 WAF 进行检测&#xff0c;如果通过检测&#xff0c;再将数据包流向主机。 二、硬件IPS/IDS防护、硬件WAF 硬件IPS/IDS防护&#xff…

VS Code 开发小技巧

VS Code的开发小技巧 添加代码片段 平时开发的时候&#xff0c;可以快速创建一个空白的模板。 一个快速生成代码片段的网站&#xff1a;https://snippet-generator.app/ 打开网站&#xff0c;把常用的模板代码复制进去&#xff0c;就会自动生成VS Code可以使用的代码片段了。…

从零到一建设数据中台 - 关键技术汇总

一、数据中台关键技术汇总 语言框架&#xff1a;Java、Maven、Spring Boot 数据分布式采集&#xff1a;Flume、Sqoop、kettle 数据分布式存储&#xff1a;Hadoop HDFS 离线批处理计算&#xff1a;MapReduce、Spark、Flink 实时流式计算&#xff1a;Storm/Spark Streaming、…

2024.05.30更新票星球抢购软件

文章目录 软件功能订阅须知早期代码软件功能 自持自定义搜索演唱会信息支持添加、删除观影人信息支持多账号并发抢票支持捡漏模式支持IP代理订阅须知 订阅后如果有问题,请联系博主,如果不懂可以免费提供讲解和远程服务早期代码 def enter_concert(self):print(u###打开浏览器…

Unity DOTS技术(一)简介

文章目录 一.概述二.将会介绍的内容三.DOTS技术与传统方式的不同传统问题DOTS技术 四.插件安装 一.概述 传统的游戏开发中,如果有成千上万的物体在场景中运动,那么你一定会认为是疯了.但有了Dost技术这一些都将变成可能.如图场景中有10000个物体在同时运动,帧率即能保持在60Fp…

Science:论文写不出来?这三个方法让你一天完成一篇论文

我是娜姐 迪娜学姐 &#xff0c;一个SCI医学期刊编辑&#xff0c;探索用AI工具提效论文写作和发表。 众所周知&#xff0c;干了学术研究这一行&#xff0c;论文就是你研究质量和数量的衡量标准&#xff0c;可以说&#xff0c;你的一切“输入”-读文献、做实验、分析数据&#x…

k8s之PV、PVC

文章目录 k8s之PV、PVC一、存储卷1、存储卷定义2、存储卷的作用2.1 数据持久化2.2 数据共享2.3 解耦2.4 灵活性 3、存储卷的分类3.1 emptyDir存储卷3.1.1 定义3.1.2 特点3.1.3 用途3.1.4 示例 3.2 hostPath存储卷3.2.1 定义3.2.2 特点3.2.3 用途3.2.4 示例 3.3 NFS存储卷3.3.1 …

【C语言】柔性数组

前言 你是否听说过柔性数组呢&#xff1f;如果没有的话&#xff0c;就一起了解一下吧。 &#xff08;没有malloc free calloc realloc 四个函数的前置知识的朋友最好先阅读一下我的“动态内存管理”一文&#xff0c;因为下面会涉及到。&#xff09; 介绍 C99中&#xff0c;…