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);
}
}
}

浙公网安备 33010602011771号