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)
在 Java 中,创建一个房间类的游戏通常涉及到构建一个简单的模拟环境,比如迷宫或者角色扮演游戏的基本结构。"房间"类可以代表游戏世界中的某个位置,它可能包含以下属性: 1. **名称**:每个房间都有一个唯一的标识名。 2. **描述**:描述房间的外观、内部物品等信息。 3. **邻居**:相邻的其他房间,表示房间之间的移动路径。 4. **物品**:房间内的道具或NPC(非玩家角色)。 5. **状态**:例如是否为开放状态,是否有特殊条件进入。 下面是一个简单的`Room`类示例: ```java public class Room { private String name; private String description; private List<Room> neighbors; private List<Item> items; public Room(String name, String description) { this.name = name; this.description = description; this.neighbors = new ArrayList<>(); this.items = new ArrayList<>(); } // getters and setters for properties public String getName() { return name; } public void setName(String name) { this.name = name; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } // 添加和获取邻居,物品操作 // ... // 检查并移步到邻居房间 public boolean moveTo(Room target) { if (neighbors.contains(target)) { // 游戏逻辑处理移动 return true; } else { return false; } } } // 示例:Item类 class Item { private String name; // 构造函数,getters and setters } // 使用Room类的方式 Room mainHall = new Room("主厅", "这是一个大堂,有通往北边的门"); Room bedroom = new Room("卧室", "这是一间舒适的卧室,墙上挂着一幅画"); mainHall.addNeighbor(bedroom); ```
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

唐沢

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

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

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

打赏作者

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

抵扣说明:

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

余额充值