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

自定义房间区域功能

效果:

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


文章中MultiMeshAreaCalculator的具体功能参考物体占用的区域及放置点自动化


功能:

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

核心功能——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=Quaternio
### 在 Unreal Engine 中使用 UMG 构建用户界面 在 Unreal Engine 中,UMG(Unreal Motion Graphics)是一个强大的工具集,用于创建和管理用户界面 (UI)。它允许开发者通过可视化编辑器快速设计复杂的 UI 元素,并将其游戏逻辑无缝集成。以下是关于如何在 Unreal Engine 中实现 UMG 用户界面的具体方法: #### 创建和编辑 UI 元素 1. **启动蓝图编辑器**:打开项目的关卡视图,在模式面板中选择“Widget Blueprint”,然后点击放置按钮来创建一个新的 Widget 资源文件[^1]。 2. **布局和样式调整**:进入新创建的 Widget 文件后,可以通过拖拽控件(如 Button、Text 或 Image)到画布上完成基本布局。支持实时预览效果并修改属性值,例如字体大小、颜色填充等参数。 #### 将 UI 元素连接至游戏逻辑 为了使 UI 动态响应玩家操作或者显示动态数据,需要建立事件驱动机制: - 使用 `Event Bind` 技术将特定的动作绑定给某个函数处理程序; - 利用变量暴露的方式让 C++ 类或其他 Blueprints 访问该对象的状态信息。 --- ### Unity UI 系统简介 相比之下,Unity 提供了一个相对简单直观但同样高效的 Canvas-Based UI System 来满足大多数需求场景下的图形展示要求。它的核心理念围绕着 Canvas 容器展开工作流——所有的视觉组件都作为子项附加在其内部节点树结构之下运行渲染管线过程之中[^4]。 主要特点如下所示: - 支持锚定系统(Anchor),使得不同分辨率下保持一致的表现形式变得容易许多; - 内置丰富的预制体(Prefab),便于重复利用常用模块而无需每次都重新定义细节部分; - 更加注重脚本编程能力拓展自定义行为的可能性无限广阔. --- ### 两者之间的对比分析 | 方面 | Unreal Engine - UMG | Unity - UI System | |--------------|---------------------------------------------|-----------------------------------------| | 工具链 | 基于蓝图(Blueprints) 和C++混合开发 | 主要依赖MonoBehaviour类编写CS代码 | | 性能表现 | 高度优化适合大型项目 | 对小型独立作品友好 | | 易学程度 | 学习曲线较陡峭 | 上手更快 | | 平台兼容性 | 多平台发布无压力 | 同样具备良好跨端移植特性 | 值得注意的是虽然二者都能很好地胜任各自擅长的应用范围内的任务挑战,但在某些特殊情况下可能还需要考虑额外因素比如团队成员的技术背景偏好等因素综合评判选用哪套方案更为合适一些[^3]. ```csharp // 示例: Unity 中简单的按钮点击事件监听 using UnityEngine; using UnityEngine.UI; public class Example : MonoBehaviour { public void OnButtonClick() { Debug.Log("Button was clicked!"); } } ``` ```cpp // 示例: UE4 中通过蓝图调用的CPP 函数声明 UFUNCTION() void HandleButtonPressed(); ```
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

唐沢

狠狠的打赏,找我拿走所有资源

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值