U_随机房间生成

RandomRoom 随即房间生成

流程大纲:

技术难点:
1、房间的生成点问题:
本方法迁移所有物体,将门临时作为整个房间的父物体,与目标门呈正对立方向,调整位置找到后,将门还原为房间的子物体。

2、检测房间重叠问题:
A:房间初始化阶段拿到合并 Mesh 的 bounds,然后生成房间四个角的相对位置的探针(后续检测重叠,因为房间有做旋转,未知旋转的角度,所以相对的世界坐标变了),见下图 1-1。
B:房间重叠检测在本文章列表中有做记录。


1-1

源码:

RoomManager 房间管理
using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using Random = UnityEngine.Random;

public class RoomManager {
    private readonly List<Room> multiDoorRoomTemplates = new List<Room>(); // 大于等于两个门的房间
    private readonly List<Room> singleDoorRoomTemplates = new List<Room>(); // 单门房间

    private readonly List<Room> roomInfoOverlapList = new List<Room>(); // 检测房间重叠的缓存信息
    private readonly List<Door> unusedDoorList = new List<Door>(); // 从开始房间到现在未使用的门

    private List<Door> tempUnusedDoorList = new List<Door>(); // 缓存当前未使用的门 
    private List<Room> tempMultiDoorRoomTemplates; // 缓存的当前多门房间模板
    private List<Room> tempSingleDoorRoomTemplates = new List<Room>(); // 缓存的当前单门房间模板

    private Door startDoor; 
    private int count;
    private bool isLoadTemplateSuc = true;

    private bool isLoadStartRoomSuc = true;
    private bool isLoadMainRoomSuc = true;
    private bool isLoadBranchRoomSuc = true;

    private static string FILENAME = "SORoomAutoGenerate";
    private static string FILEPATH = "Config/SO/";
    private static string TYPENAME = "SORoomAutoGenerate";

    private RoomGenerateInfo roomGenerateInfo;
    private SORoomAutoGenerate config;
    private Vector3 playerInitialPoint;
    public RoomManager() {
        LoadConfig();
        LoadTemplate();
        if (isLoadTemplateSuc) {
            if (config.IsGenerateRoom) {
                CreateStartRoom(); // 开始房间
                CreateMainRoom(); // 主分支,房间模板起码两个门
                CreateBossRoom(); // 挑战分支,房间模板为单个门
                SaveRoomInfo();
            } else {
                LoadRoom();
            }
        }
    }

    private void LoadConfig() {
        config = Resources.Load<SORoomAutoGenerate>(FILEPATH + FILENAME);
        if (!config) {
            var type = System.Reflection.Assembly.Load("Assembly-CSharp").GetType(TYPENAME);
            config = Activator.CreateInstance(type) as SORoomAutoGenerate;
            LogS($"创建配置文件: {FILENAME}.asset => 路径: {FILEPATH}");
            AssetDatabase.CreateAsset(config, $"Assets/Resources/" + $"{FILEPATH}{FILENAME}.asset");
        }

        if (!config) {
            LogE("没有找到自动生成房间配置文件");
        }
    }

    /// <summary>
    /// 加载模板
    /// </summary>
    private void LoadTemplate() {
        var roomTemplates = config.RoomTemplates;
        for (var i = 0; i < roomTemplates.Count; i++) {
            var template = roomTemplates[i];
            if (template.doorPos.Count == 0) {
                LogE("模板名称: " + template.name + " 房间创建生成点为空!");
                isLoadTemplateSuc = false;
                return;
            }

            if (template.doorPos.Count == 1) {
                if (singleDoorRoomTemplates.Contains(template)) {
                    LogE("模板名称: " + template.name + " 在 RoomManager 中配置重复!");
                    isLoadTemplateSuc = false;
                    return;
                }

                singleDoorRoomTemplates.Add(template);
                LogS("成功添加单门房间模板:" + template.name);
                continue;
            }

            if (template.doorPos.Count > 1) {
                if (multiDoorRoomTemplates.Contains(template)) {
                    LogE("模板名称: " + template.name + " 在 RoomManager 中配置重复!");
                    isLoadTemplateSuc = false;
                    return;
                }
                multiDoorRoomTemplates.Add(template);
                LogS("成功添加多门房间模板:" + template.name);
            }
        }
    }

    /// <summary>
    /// 保存房屋数据
    /// </summary>
    private void SaveRoomInfo() {
        var roomMultiCount = config.RoomMultiCount;
        var roomSingleCount = config.RoomSingleCount;
        if (isLoadStartRoomSuc && isLoadMainRoomSuc && isLoadBranchRoomSuc) {
            if (roomInfoOverlapList.Count == roomMultiCount + roomSingleCount + 1) {
                config.RoomCreateInfoList.Add(roomGenerateInfo);
                LogS("房间保存成功");
            } else {
                LogE($"生成房间个数有差异,具体:当前创建房间个数:{roomMultiCount + roomSingleCount + 1}, 开始房间个数:1, Boss 房间个数:{roomSingleCount} , ");
            }
        } else {
            LogE($"保存失败有环节错误打断");
        }
    }

    /// <summary>
    /// 加载房间
    /// </summary>
    private void LoadRoom() {
        var soRoomCreateInfo = Resources.Load<SORoomAutoGenerate>(FILEPATH + FILENAME);
        var list = soRoomCreateInfo.RoomCreateInfoList;
        if(list.Count > 0) {
            var createInfo = list[Random.Range(0, list.Count)];
            if (createInfo.RoomDataInfos.Count > 0) {
                var infos = createInfo.RoomDataInfos;
                for (var i = 0; i < infos.Count; i++) {
                    var data = infos[i];
                    Room roomTemplate;
                    if (data.templateType == 1) {
                        roomTemplate = singleDoorRoomTemplates[data.templateIndex];
                    } else {
                        roomTemplate = multiDoorRoomTemplates[data.templateIndex];
                    }
                    if (roomTemplate) {
                        var room = CreateRoom(roomTemplate, data.roomVec, data.roomQua);
                        room.name = "tempRoom_" + i;
                        if (data.isStartRoom == 1) {
                            playerInitialPoint = data.playerVec;
                            room.Floor.GetComponent<Renderer>().material.color = Color.green;
                            continue;
                        }

                        if (data.templateType == 1) {
                            room.Floor.GetComponent<Renderer>().material.color = Color.yellow;
                        }
                    }
                }
            } else {
                LogE("房间数据列表数量为空");
            }
        } else {
            LogE("房间生成列表为空");
        }
    }

    #region 日志

    public static void LogE(string content) {
        Debug.LogError("【错误】" + content);
        EditorApplication.isPaused = true;
    }
    
    public static void LogS(string content) {
        Debug.Log("【成功】" + content);
    }

    public static void Log(string content) {
        Debug.Log(content);
    }

    #endregion

    /// <summary>
    ///  获取玩家起始点
    /// </summary>
    public Vector3 GetStartRoomPlayerPoint() {
        return playerInitialPoint;
    }

    /// <summary>
    /// 创建初始房间
    /// </summary>
    private void CreateStartRoom() {
        if (singleDoorRoomTemplates.Count == 0) {
            isLoadStartRoomSuc = false;
            LogE("单个门的房间模板为空 开始房间创建失败");
            return;
        }

        // ######### 开始房间 ##########
        if (!startDoor) {
            var index = Random.Range(0, singleDoorRoomTemplates.Count);
            var initialPoint = config.InitialPoint;
            var tempRoom = CreateRoom(singleDoorRoomTemplates[index], initialPoint.position, initialPoint.rotation);
            // tempRoom.transform.position = initialPoint.position;
            // tempRoom.transform.rotation = initialPoint.rotation;

            tempRoom.name = "startRoom";
            tempRoom.Floor.GetComponent<Renderer>().material.color = Color.green;

            playerInitialPoint = tempRoom.roomBounds.center;
            startDoor = GetRandom(tempRoom.DoorList);
            startDoor.GetComponentInChildren<Renderer>().material.color = Color.red;
            roomInfoOverlapList.Add(tempRoom);
            tempUnusedDoorList.Add(startDoor);

            roomGenerateInfo = new RoomGenerateInfo();
            roomGenerateInfo.RoomDataInfos.Add(new RoomDataInfo() {
                isStartRoom = 1,
                templateType = 1,
                templateIndex = index,
                roomVec = tempRoom.transform.position,
                roomQua = tempRoom.transform.rotation,
                playerVec = tempRoom.roomBounds.center,
            });

            LogS("创建开始房间: " + tempRoom.name);
        }
    }
    
    /// <summary>
    /// 创建主分支 - 门个数大于等于2
    /// </summary>
    private void CreateMainRoom() {
        if (isLoadStartRoomSuc) {
            if (multiDoorRoomTemplates.Count == 0) {
                isLoadMainRoomSuc = false;
                LogE("大于两个门的房间模板为空");
                return;
            }

            for (var i = config.RoomMultiCount; i > 0; i--) {
                tempMultiDoorRoomTemplates = new List<Room>(multiDoorRoomTemplates);
                count = 0;
                CreateMultiRoom();
            }
        }
    }

    /// <summary>
    /// 创建 Boss 分支 - 门个数为1
    /// </summary>
    private void CreateBossRoom() {
        if (isLoadMainRoomSuc) {
            var roomSingleCount = config.RoomSingleCount;
            if (roomSingleCount <= 0) {
                Log("未设置创建单门房间");
            } else {
                if (singleDoorRoomTemplates.Count == 0) {
                    isLoadBranchRoomSuc = false;
                    LogE("单个门房间模板为空");
                } else {
                    for (var i = 0; i < roomSingleCount; i++) {
                        tempSingleDoorRoomTemplates = new List<Room>(singleDoorRoomTemplates);
                        count = 0;
                        CreateSingleRoom();
                    }
                }
            }
        }
    }

    
    /// <summary>
    ///  创建 单门房间
    /// </summary>
    private void CreateSingleRoom() {
        if (count > 10) {
            isLoadBranchRoomSuc = false;
            LogE($"单门房间尝试创建次数大于 {count} 次");
            return;
        }

        count++;

        if (tempSingleDoorRoomTemplates.Count == 0 || unusedDoorList.Count == 0) {
            isLoadBranchRoomSuc = false;
            LogE("创建单门房间失败所有可能情况重叠");
            return;
        }

        var randTemplate = GetRandom(tempSingleDoorRoomTemplates);
        var index = GetIndex(randTemplate, singleDoorRoomTemplates);
        var initialPoint = config.InitialPoint;
        var tempRoom = CreateRoom(randTemplate, initialPoint.position, initialPoint.rotation);

        var entranceDoor = tempRoom.DoorList[0];

        var targetDoor = GetRandom(unusedDoorList);
        for (var i = 0; i < unusedDoorList.Count; i++) {
            unusedDoorList[i].GetComponentInChildren<Renderer>().material.color = Color.black;
        }

        tempRoom.Part.SetParent(entranceDoor.transform);
        entranceDoor.transform.SetParent(null);
        tempRoom.transform.SetParent(entranceDoor.transform);

        SetEntranceDoor(targetDoor, entranceDoor);

        tempRoom.transform.SetParent(null);
        entranceDoor.transform.SetParent(tempRoom.transform);

        if (CheckRoomInfoOverlap(tempRoom)) {
            GameObject.Destroy(tempRoom);
            if (tempSingleDoorRoomTemplates.Count > 0) {
                tempSingleDoorRoomTemplates.Remove(randTemplate);
                CreateSingleRoom();
            }
            return;
        }

        tempRoom.Floor.GetComponent<Renderer>().material.color = Color.yellow;
        roomInfoOverlapList.Add(tempRoom);
        unusedDoorList.Remove(targetDoor);

        roomGenerateInfo.RoomDataInfos.Add(new RoomDataInfo() {
            isStartRoom = 0,
            templateType = 1,
            templateIndex = index,
            roomVec = tempRoom.transform.position,
            roomQua = tempRoom.transform.rotation,
        });
    }

    private void CreateMultiRoom() {
        if (count > 10) {
            isLoadMainRoomSuc = false;
            LogE($"多门房间尝试创建次数大于 {count} 次");
            return;
        }

        count++;

        if (tempMultiDoorRoomTemplates.Count == 0 || tempUnusedDoorList.Count == 0) {
            isLoadMainRoomSuc = false;
            LogE("创建多门房间失败所有可能情况重叠");
            return;
        }

        var roomTemplate = GetRandom(tempMultiDoorRoomTemplates);
        var index = GetIndex(roomTemplate, multiDoorRoomTemplates);
        var initialPoint = config.InitialPoint;
        var tempRoom = CreateRoom(roomTemplate, initialPoint.position, initialPoint.rotation);

        tempRoom.name = "tempRoom_" + roomInfoOverlapList.Count;
        Log("初步创建多门房间:" + tempRoom.name);

        var entranceDoor = GetRandom(tempRoom.DoorList);

        for (var k = 0; k < tempRoom.DoorList.Count; k++) {
            var door = tempRoom.DoorList[k];
            if (door != entranceDoor) {
                door.transform.SetParent(tempRoom.Part);
            }
        }

        tempRoom.Part.SetParent(entranceDoor.transform);
        entranceDoor.transform.SetParent(null);
        tempRoom.transform.SetParent(entranceDoor.transform);

        var targetDoor = GetRandom(tempUnusedDoorList);

        SetEntranceDoor(targetDoor, entranceDoor);
        
        tempRoom.transform.SetParent(null);
        entranceDoor.transform.SetParent(tempRoom.transform);

        // ######### 检测重叠 ##########

        if (CheckRoomInfoOverlap(tempRoom)) {
            GameObject.Destroy(tempRoom.gameObject);

            if (tempUnusedDoorList.Count > 0) {
                tempUnusedDoorList.Remove(targetDoor);
                CreateMultiRoom();
                return;
            }

            if (tempMultiDoorRoomTemplates.Count > 0){
                tempMultiDoorRoomTemplates.Remove(roomTemplate);
                CreateMultiRoom();
                return;
            }
        }
        
        LogS("创建多门房间:" + tempRoom.name);

        targetDoor.GetComponentInChildren<Renderer>().material.color = Color.red;
        entranceDoor.GetComponentInChildren<Renderer>().material.color = Color.red;

        roomGenerateInfo.RoomDataInfos.Add(new RoomDataInfo() {
            isStartRoom = 0,
            templateType = 2,
            templateIndex = index,
            roomVec = tempRoom.transform.position,
            roomQua = tempRoom.transform.rotation,
        });

        tempUnusedDoorList = new List<Door>(tempRoom.DoorList);
        tempUnusedDoorList.Remove(entranceDoor);
        
        roomInfoOverlapList.Add(tempRoom);

        for (var i = 0; i < tempRoom.DoorList.Count; i++) {
            var tempDoor = tempRoom.DoorList[i];
            if (tempDoor != entranceDoor) {
                unusedDoorList.Add(tempDoor);
                unusedDoorList.Remove(targetDoor);
            }
        }
    }

    private int GetIndex(Room room, List<Room> roomList) {
        for (int i = 0; i < roomList.Count; i++) {
            var tempTemplate = roomList[i];
            if (tempTemplate == room) {
                return i;
            }
        }

        return -1;
    }

    /// <summary>
    /// 获取列表随机值
    /// </summary>
    /// <param name="list">列表</param>
    /// <typeparam name="T">类型</typeparam>
    /// <returns></returns>
    private T GetRandom<T>(List<T> list){
        return list[Random.Range(0, list.Count)];
    }
    
    /// <summary>
    /// 创建房间
    /// </summary>
    /// <param name="roomTemplate"></param>
    /// <param name="pos"></param>
    /// <param name="rot"></param>
    /// <returns></returns>
    private Room CreateRoom(Room roomTemplate, Vector3 pos, Quaternion rot) {
        var tempRoomObj = GameObject.Instantiate(roomTemplate.gameObject, pos, rot);
        var tempRoom = tempRoomObj.GetComponent<Room>();
        tempRoom.OnInit();
        return tempRoom;
    }

    /// <summary>
    /// 设置入口门 - 移动 entranceDoor 到 targetDoor 位置且将 entranceDoor 90度旋转直到和 targetDoor 处于正相反的位置
    /// </summary>
    /// <param name="targetDoor">目标门</param>
    /// <param name="entranceDoor">入口门</param>
    /// <returns>entranceDoor 是否可以旋转到 targetDoor 的正相反方向</returns>
    void SetEntranceDoor(Door targetDoor, Door entranceDoor) {
        entranceDoor.transform.position = targetDoor.transform.position + targetDoor.transform.forward * 0.1f;
        var num = 0;
        while (num < 4) {
            num++;
            var dot = Vector3.Dot(targetDoor.transform.forward, entranceDoor.transform.forward);
            if (Math.Abs(dot - (-1)) < 0.01f) {
                return;
            }
            entranceDoor.transform.rotation *= Quaternion.Euler(0, 90, 0);
        }
    }

    /// <summary>
    ///  检测重叠
    /// </summary>
    /// <param name="room"></param>
    /// <returns></returns>
    bool CheckRoomInfoOverlap(Room room) {
        for (var i = 0; i < roomInfoOverlapList.Count; i++) {
            var curRoom = roomInfoOverlapList[i];
            if (IsOverlap(curRoom, room)) {
                LogE("房间重叠: 本体房间:" + room.name + ",检测列表中选中房间:" + curRoom.name);
                return true;
            }
        }

        return false;
    }

    /// <summary>
    /// 房间是否重叠
    /// </summary>
    bool IsOverlap(Room r1, Room r2) {
        var r1Vec = GetCheckRoomSizeTran(r1).position;
        var r2Vec = GetCheckRoomSizeTran(r2).position;
        var ma = r1Vec.x + r1.roomBounds.size.x < r2Vec.x || r2Vec.x + r2.roomBounds.size.x < r1Vec.x;
        var mb = r1Vec.y + r1.roomBounds.size.y < r2Vec.y || r2Vec.y + r2.roomBounds.size.y < r1Vec.y;
        var mc = r1Vec.z + r1.roomBounds.size.z < r2Vec.z || r2Vec.z + r2.roomBounds.size.z < r1Vec.z;
        return !ma && !mb && !mc;
    }

    /// <summary>
    /// 获取与世界坐标相同的房屋检测位置信息
    /// </summary>
    /// <param name="room"></param>
    /// <returns></returns>
    Transform GetCheckRoomSizeTran(Room room) {
        for (var i = 0; i < room.RoomOverlapPosList.Count; i++) {
            var temp = room.RoomOverlapPosList[i];
            if (temp.right == Vector3.right && temp.up == Vector3.up && temp.forward == Vector3.forward) {
                return temp;
            }
        }
        
        LogE("房间名称:[" + room.name + "] 没有找到和世界坐标对其的检测坐标,请检查!");
        return null;
    }
}

SORoomAutoGenerate 房间配置
using System;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 房间数据
/// </summary>
[Serializable]
public class RoomDataInfo {
    public int isStartRoom;
    public int templateType;
    public int templateIndex;
    public Vector3 roomVec;
    public Quaternion roomQua;
    public Vector3 playerVec; // 玩家生成位置
}

/// <summary>
/// 房间生成数据
/// </summary>
[Serializable]
public class RoomGenerateInfo {
    /// <summary>
    /// 房间数据列表
    /// </summary>
    public List<RoomDataInfo> RoomDataInfos = new List<RoomDataInfo>();
}

[CreateAssetMenu(menuName = "RandomRoom/SORoomAutoGenerate")]
public class SORoomAutoGenerate : ScriptableObject {
    [Tooltip("生成房间 (是:房间生成自动存入列表/否:列表随机创建房间)")]
    public bool IsGenerateRoom;

    [Tooltip("房间模板")]
    public List<Room> RoomTemplates;

    [Tooltip("房间创建个数(多)")]
    public int RoomMultiCount;

    [Tooltip("房间创建个数(单)")]
    public int RoomSingleCount;
    
    [Tooltip("房间起始点")]
    public Transform InitialPoint;

    /// <summary>
    ///  房间生成列表
    /// </summary>
    public List<RoomGenerateInfo> RoomCreateInfoList = new List<RoomGenerateInfo>();
}
Room 房间
using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;

public class Room : MonoBehaviour {
    public List<Transform> doorPos;

    [SerializeField] private Door doorTemplate;
    [NonSerialized] public readonly List<Door> DoorList = new List<Door>();
    [NonSerialized] public Transform Part;

    public List<Transform> RoomOverlapPosList;
    public Transform Floor;
    public Transform RoomCombineMesh;
    public Bounds roomBounds;
    public void OnInit() {
        // 获取第一层级所有组件
        var tempTransforms = new List<Transform>();
        foreach (Transform tempTran in transform) {
            tempTransforms.Add(tempTran);
        }

        // 创建 Part
        Part = new GameObject("Part").transform;
        Part.position = transform.position;
        Part.rotation = transform.rotation;
        Part.SetParent(transform);

        foreach (var trans in tempTransforms) {
            trans.SetParent(Part.transform);
        }

        // 创建 Bounds 生成 监测点
        CreateRoomOverlapPos();

        for (var i = 0; i < doorPos.Count; i++) {
            var pos = doorPos[i];
            DoorList.Add(Instantiate(doorTemplate.gameObject, pos.position, pos.rotation, transform).GetComponent<Door>());
            pos.gameObject.SetActive(false);
        }
    }

    private void CreateRoomOverlapPos() {
        roomBounds = RoomCombineMesh.GetComponent<Renderer>().bounds;

        var RoomOverlapPos = new GameObject("RoomOverlapPos1").transform;
        RoomOverlapPos.position = roomBounds.center + new Vector3(-roomBounds.size.x, -roomBounds.size.y, -roomBounds.size.z) / 2;
        RoomOverlapPosList.Add(RoomOverlapPos);

        RoomOverlapPos = new GameObject("RoomOverlapPos2").transform;
        RoomOverlapPos.position = roomBounds.center + new Vector3(roomBounds.size.x, -roomBounds.size.y, -roomBounds.size.z) / 2;
        RoomOverlapPos.localRotation = Quaternion.Euler(0, -90, 0);
        RoomOverlapPosList.Add(RoomOverlapPos);

        RoomOverlapPos = new GameObject("RoomOverlapPos3").transform;
        RoomOverlapPos.position = roomBounds.center + new Vector3(roomBounds.size.x, -roomBounds.size.y, roomBounds.size.z) / 2;
        RoomOverlapPos.localRotation = Quaternion.Euler(0, -180, 0);
        RoomOverlapPosList.Add(RoomOverlapPos);

        RoomOverlapPos = new GameObject("RoomOverlapPos4").transform;
        RoomOverlapPos.position = roomBounds.center + new Vector3(-roomBounds.size.x, -roomBounds.size.y, roomBounds.size.z) / 2;
        RoomOverlapPos.localRotation = Quaternion.Euler(0, 90, 0);
        RoomOverlapPosList.Add(RoomOverlapPos);

        for (var i = 0; i < RoomOverlapPosList.Count; i++) {
            RoomOverlapPosList[i].SetParent(Part);
        }
    }
}
posted @ 2022-04-16 18:50  匿鱼  阅读(201)  评论(0)    收藏  举报