【unity實戰】使用Unity程序化生成3D隨機地牢(附項目源碼)

最終效果

在這里插入圖片描述

文章目錄

  • 最終效果
  • 前言
    • 1、理解程序生成的核心概念
    • 2、種子值的核心作用
    • 3、程序生成的實際應用
    • 4、主流程序生成技術概覽
    • 5、選擇合適的技術
  • 實戰
    • 1、素材
    • 2、生成一面墻變換矩陣數據
    • 3、渲染墻壁
    • 4、加點隨機不同的墻壁效果
    • 5、繪制四面墻壁
    • 4、在四個角落生成支柱
    • 5、生成地板
    • 6、墻壁接縫生成支柱
    • 7、為每個實例生成GameObject
    • 8、生成碰撞體
    • 9、放置隨機物品
    • 10、設置隨機種子
  • 源碼
  • 參考
  • 專欄推薦
  • 完結

前言

1、理解程序生成的核心概念

程序生成(Procedural Generation)常被誤解為隨機生成,但二者有本質區別。程序生成是通過預設規則系統來創造內容,雖然結果可能看似不可預測,但實際上是確定性的。這種技術讓我們能夠精確控制輸出內容的類型和特征。

2、種子值的核心作用

種子(Seed)是程序生成中的關鍵概念:

  • 作為隨機數生成的起點值

  • 保證生成結果的可重復性

  • 相同的種子必然產生相同的輸出序列

實際上,計算機中的"隨機"函數是偽隨機的確定性算法,通過對種子值進行數學變換來產生看似隨機的序列。這種設計確保了結果的可靠性。

如果使用相同的種子,每次都會得到相同的隨機值序列,這對于創建可重復的輸出很有用。
在這里插入圖片描述

3、程序生成的實際應用

程序生成在內容創作中(特別是游戲開發)有著廣泛應用,其實現可分為兩個層面:

  • 數據生成層 - 使用算法創造原始數據

  • 內容呈現層 - 將數據轉化為可視/可交互內容

4、主流程序生成技術概覽

技術典型應用場景特點
基于語法的生成建筑、城市生成使用規則系統定義結構
L-Systems植物、樹木生成遞歸應用規則生成復雜形態
波函數坍縮地形、圖案生成通過約束傳播生成連貫結構
細胞自動機地形、紋理生成基于簡單規則產生復雜行為
噪聲函數自然地形、紋理產生有機的隨機模式
二元空間分區室內空間劃分遞歸分割空間創建布局

5、選擇合適的技術

沒有放之四海而皆準的解決方案,開發者應該:

  1. 廣泛了解各種生成技術
  2. 將其視為工具箱中的不同工具
  3. 通過經驗積累培養技術選型能力

實戰

1、素材

https://assetstore.unity.com/packages/3d/environments/dungeons/lite-dungeon-pack-low-poly-3d-art-by-gridness-242692
在這里插入圖片描述

2、生成一面墻變換矩陣數據

//生成地牢函數
public class GenerateDungeon : MonoBehaviour
{[Header("房間尺寸(X為寬度,Y為長度)")]public Vector2 roomSize = new Vector2(10, 10);[Header("墻壁生成相關")]public Mesh wallMesh; // 普通墻壁網格public Material wallMaterial; // 墻壁材質List<Matrix4x4> _wallMatricesN; // 普通墻壁變換矩陣列表void Start(){CreateWalls();}// 創建墻面物體的函數void CreateWalls(){// 初始化存儲墻面變換矩陣的列表_wallMatricesN = new List<Matrix4x4>();// 計算需要的墻面數量:房間寬度除以單面墻寬度,至少1面墻int wallCount = Mathf.Max(1, (int)(roomSize.x / wallMesh.bounds.size.x));// 計算每面墻的縮放比例:使墻面正好填滿房間寬度float scale = (roomSize.x / wallCount) / wallMesh.bounds.size.x;// 循環生成每面墻的變換矩陣for (int i = 0; i < wallCount; i++){// 計算墻面位置:// 1. 從房間左邊界開始(-roomSize.x/2)// 2. 加上半面墻寬度(wallSize.x*scale/2)// 3. 加上當前墻的偏移量(i*scale*wallSize.x)// Y軸位置為房間上邊界(+roomSize.y/2)Vector3 position = transform.position +new Vector3(-roomSize.x / 2 + wallMesh.bounds.size.x * scale / 2 +i * scale * wallMesh.bounds.size.x,0,+roomSize.y / 2);// 使用父物體的旋轉Quaternion rotation = transform.rotation;// 設置縮放:X軸按計算比例縮放,Y和Z軸保持原大小Vector3 scaleVec = new Vector3(scale, 1, 1);// 創建變換矩陣(位置/旋轉/縮放)Matrix4x4 matrix = Matrix4x4.TRS(position, rotation, scaleVec);// 將矩陣添加到列表_wallMatricesN.Add(matrix);}}
}

3、渲染墻壁

在 Unity 中,Graphics.DrawMeshInstanced 是一種高性能的渲染技術,特別適用于需要繪制大量相同或相似網格(如地牢墻壁、植被、子彈等)的場景。

void Update()
{RenderWalls();
}// 渲染墻面物體的函數
void RenderWalls()
{// 渲染普通墻體if (_wallMatricesN != null && _wallMatricesN.Count > 0){Graphics.DrawMeshInstanced(wallMesh,0,wallMaterial,_wallMatricesN.ToArray(),_wallMatricesN.Count);}
}

效果
在這里插入圖片描述

4、加點隨機不同的墻壁效果

public Mesh wallMeshB; // 特殊墻壁網格
List<Matrix4x4> _wallMatricesNB; // 特殊墻壁變換矩陣列表void CreateWalls()
{// 初始化列表,用于存儲不同類型墻壁的變換矩陣_wallMatricesN = new List<Matrix4x4>();   // 普通墻壁_wallMatricesNB = new List<Matrix4x4>();  // 類型B墻壁// 計算沿x軸需要放置的墻壁數量,至少為1面墻int wallCount = Mathf.Max(1, (int)(roomSize.x / wallMesh.bounds.size.x));// 計算墻壁的縮放比例,使墻壁正好填滿房間的x軸長度float scale = (roomSize.x / wallCount) / wallMesh.bounds.size.x;// 循環創建每一面墻壁for (int i = 0; i < wallCount; i++){// 計算墻壁位置:從房間左側開始,等間距排列var t = transform.position = new Vector3(-roomSize.x / 2 + wallMesh.bounds.size.x * scale / 2 + i * scale * wallMesh.bounds.size.x, // x位置0,                                                                 // y位置-roomSize.y / 2);                                                  // z位置// 使用當前物體的旋轉var r = transform.rotation;// 設置縮放:x軸根據計算的比例縮放,y和z保持原樣var s = new Vector3(scale, 1, 1);// 創建變換矩陣(位置、旋轉、縮放)var mat = Matrix4x4.TRS(t, r, s);// 隨機決定墻壁類型(0或1)var rand = Random.Range(0, 2);// 根據隨機數將墻壁分配到不同的列表if (rand < 1){_wallMatricesN.Add(mat);   // 類型0 - 普通墻壁}else if (rand < 2){_wallMatricesNB.Add(mat);  // 類型1 - B類墻壁}}
}// 渲染墻面物體的函數
void RenderWalls()
{// 。。。// 渲染B類型墻體if (_wallMatricesNB != null && _wallMatricesNB.Count > 0){Graphics.DrawMeshInstanced(wallMeshB,0,wallMaterial,_wallMatricesNB.ToArray(),_wallMatricesNB.Count);}
}

效果
在這里插入圖片描述

5、繪制四面墻壁

每面墻壁的生成非常類似,我們可以把生成單面墻壁進行封裝

// 創建所有墻壁
void CreateWalls()
{_wallMatricesN = new List<Matrix4x4>();_wallMatricesNB = new List<Matrix4x4>();// 生成四條邊的墻體CreateWallEdge(Vector3.forward, roomSize.y / 2, false);    // 北墻CreateWallEdge(Vector3.back, roomSize.y / 2, false);       // 南墻CreateWallEdge(Vector3.right, roomSize.x / 2, true);      // 東墻CreateWallEdge(Vector3.left, roomSize.x / 2, true);       // 西墻
}// 創建單邊墻體
void CreateWallEdge(Vector3 direction, float offset, bool isVertical)
{// 確定墻體方向Vector3 axis = isVertical ? Vector3.up : Vector3.zero;float rotationAngle = isVertical ? 90f : 0f;Vector2 size = isVertical ? new Vector2(roomSize.y, 0) : new Vector2(roomSize.x, 0);// 計算墻體數量和縮放比例int wallCount = Mathf.Max(1, (int)(size.x / wallMesh.bounds.size.x));float scale = (size.x / wallCount) / wallMesh.bounds.size.x;// 創建該邊的所有墻體for (int i = 0; i < wallCount; i++){// 計算墻體位置Vector3 position = transform.position + direction * offset;if (isVertical){position += Vector3.forward * (-size.x / 2 + wallMesh.bounds.size.x * scale / 2 + i * scale * wallMesh.bounds.size.x);}else{position += Vector3.right * (-size.x / 2 + wallMesh.bounds.size.x * scale / 2 + i * scale * wallMesh.bounds.size.x);}// 設置旋轉和縮放Quaternion rotation = transform.rotation * Quaternion.AngleAxis(rotationAngle, axis);Vector3 scaleVec = new Vector3(scale, 1, 1);// 創建變換矩陣Matrix4x4 matrix = Matrix4x4.TRS(position, rotation, scaleVec);// 隨機分配墻體類型if (Random.Range(0, 2) == 0)_wallMatricesN.Add(matrix);else_wallMatricesNB.Add(matrix);}
}

效果
在這里插入圖片描述

4、在四個角落生成支柱

#region 支柱生成相關
[Header("支柱生成相關")]
public Mesh pillarMesh; // 支柱網格
public Material pillarMaterial; // 支柱材質
List<Matrix4x4> _pillarMatrices; // 支柱變換矩陣列表//在四個角落添加支柱
void CreatePillars()
{_pillarMatrices = new List<Matrix4x4>();// 定義房間四個角落的位置Vector3[] corners = new Vector3[]{new Vector3(-roomSize.x/2, 0, -roomSize.y/2), // 西南角new Vector3(-roomSize.x/2, 0, roomSize.y/2),  // 西北角new Vector3(roomSize.x/2, 0, -roomSize.y/2),  // 東南角new Vector3(roomSize.x/2, 0, roomSize.y/2)    // 東北角};// 為每個角落創建支柱foreach (Vector3 corner in corners){Vector3 position = transform.position + corner;Quaternion rotation = transform.rotation;Vector3 scale = Vector3.one; // 支柱默認縮放Matrix4x4 matrix = Matrix4x4.TRS(position, rotation, scale);_pillarMatrices.Add(matrix);}
}// 渲染支柱
void RenderPillars()
{if (_pillarMatrices != null && _pillarMatrices.Count > 0){Graphics.DrawMeshInstanced(pillarMesh,0,pillarMaterial,_pillarMatrices.ToArray(),_pillarMatrices.Count);}
}#endregion

效果
在這里插入圖片描述

5、生成地板

生成地板可以使用兩種模式——縮放方式平鋪方式,具體選擇其一即可

#region  地板生成相關
[Header("地板生成相關")]
public Mesh floorMesh; // 地板網格
public Material floorMaterial; // 地板材質
List<Matrix4x4> _floorMatrices; // 地板變換矩陣列表// 創建地板(縮放方式)
void createFloorScale()
{_floorMatrices = new List<Matrix4x4>();// 計算地板位置(房間中心)Vector3 position = transform.position;Quaternion rotation = transform.rotation;// 根據房間尺寸縮放地板Vector3 scale = new Vector3(roomSize.x, 1, roomSize.y);Matrix4x4 matrix = Matrix4x4.TRS(position, rotation, scale);_floorMatrices.Add(matrix);
}// 創建地板(平鋪方式)
void CreateFloorTile()
{_floorMatrices = new List<Matrix4x4>();// 獲取地板網格的原始尺寸float floorWidth = floorMesh.bounds.size.x;float floorLength = floorMesh.bounds.size.z;// 計算X軸和Z軸需要的地板數量int floorCountX = Mathf.Max(1, Mathf.CeilToInt(roomSize.x / floorWidth));int floorCountZ = Mathf.Max(1, Mathf.CeilToInt(roomSize.y / floorLength)); // 注意:RoomSize.y對應的是Z軸長度// 計算實際縮放比例(使地板正好鋪滿房間)float scaleX = (roomSize.x / floorCountX) / floorWidth;float scaleZ = (roomSize.y / floorCountZ) / floorLength;// 起始位置(左下角)Vector3 startPos = transform.position + new Vector3(-roomSize.x / 2 + floorWidth * scaleX / 2, 0, -roomSize.y / 2 + floorLength * scaleZ / 2);// 平鋪地板for (int x = 0; x < floorCountX; x++){for (int z = 0; z < floorCountZ; z++){// 計算每塊地板的位置Vector3 position = startPos + new Vector3(x * floorWidth * scaleX,0,z * floorLength * scaleZ);// 保持默認旋轉Quaternion rotation = transform.rotation;// 設置縮放(保持Y軸不變)Vector3 scale = new Vector3(scaleX, 1, scaleZ);// 創建變換矩陣Matrix4x4 matrix = Matrix4x4.TRS(position, rotation, scale);_floorMatrices.Add(matrix);}}
}// 渲染地板
void RenderFloor()
{if (_floorMatrices != null && _floorMatrices.Count > 0){Graphics.DrawMeshInstanced(floorMesh,0,floorMaterial,_floorMatrices.ToArray(),_floorMatrices.Count);}
}
#endregion

效果
在這里插入圖片描述

6、墻壁接縫生成支柱

#region 墻壁接縫支柱
[Header("墻壁接縫支柱")]
public Mesh junctionPillarMesh; // 接縫支柱網格
public Material junctionPillarMaterial; // 接縫支柱材質
List<Matrix4x4> _junctionPillarMatrices; // 接縫支柱變換矩陣void AddWallJunctionPillars()
{if (junctionPillarMesh == null) return;_junctionPillarMatrices = new List<Matrix4x4>();// 獲取墻體網格尺寸(假設所有墻體網格尺寸相同)float wallWidth = wallMesh.bounds.size.x;// 計算水平和垂直墻體的數量int horizontalWallCount = Mathf.Max(1, (int)(roomSize.x / wallWidth));int verticalWallCount = Mathf.Max(1, (int)(roomSize.y / wallWidth));// 計算墻體縮放比例float hScale = (roomSize.x / horizontalWallCount) / wallWidth;float vScale = (roomSize.y / verticalWallCount) / wallWidth;// 添加南北墻的接縫支柱(東西走向的墻)for (int i = 1; i < horizontalWallCount; i++){// 北墻接縫float xPos = -roomSize.x / 2 + i * wallWidth * hScale;AddPillar(transform.position + new Vector3(xPos, 0, roomSize.y / 2));// 南墻接縫AddPillar(transform.position + new Vector3(xPos, 0, -roomSize.y / 2));}// 添加東西墻的接縫支柱(南北走向的墻)for (int i = 1; i < verticalWallCount; i++){// 東墻接縫float zPos = -roomSize.y / 2 + i * wallWidth * vScale;AddPillar(transform.position + new Vector3(roomSize.x / 2, 0, zPos));// 西墻接縫AddPillar(transform.position + new Vector3(-roomSize.x / 2, 0, zPos));}
}// 在指定位置添加支柱
void AddPillar(Vector3 position, float scaleMultiplier = 1f)
{Quaternion rotation = transform.rotation;Vector3 scale = Vector3.one * scaleMultiplier;_junctionPillarMatrices.Add(Matrix4x4.TRS(position, rotation, scale));
}// 渲染接縫支柱
void RenderJunctionPillars()
{if (_junctionPillarMatrices != null && _junctionPillarMatrices.Count > 0){Graphics.DrawMeshInstanced(junctionPillarMesh,0,junctionPillarMaterial,_junctionPillarMatrices.ToArray(),_junctionPillarMatrices.Count);}
}
#endregion

效果,這里我沒有特殊支柱的素材,就還有用四個角落的支柱代替好了
在這里插入圖片描述

7、為每個實例生成GameObject

這個一般只在需要的使用,如果你想更靈活的操作自己的對象,比如實現可破壞的墻壁效果等等,這里我以墻體為例,其他大家自行擴展即可,方法都一樣。

#region 為每個實例生成GameObject
// 修改后的創建方法示例(以墻體為例)
List<GameObject> _wallInstances = new List<GameObject>();void CreateWallsWithColliders()
{// 銷毀舊實例(如果存在)foreach (var wall in _wallInstances) Destroy(wall);_wallInstances.Clear();// 生成帶碰撞體的墻體foreach (var matrix in _wallMatricesN){GameObject wall = new GameObject("WallInstance");wall.transform.SetPositionAndRotation(matrix.GetPosition(), matrix.rotation);wall.transform.localScale = matrix.lossyScale;// 添加網格組件MeshFilter filter = wall.AddComponent<MeshFilter>();filter.mesh = wallMesh;MeshRenderer renderer = wall.AddComponent<MeshRenderer>();renderer.material = wallMaterial;// 添加碰撞體(根據需求選擇類型)MeshCollider collider = wall.AddComponent<MeshCollider>();collider.sharedMesh = wallMesh;collider.convex = false; // 對于靜態墻體設為false_wallInstances.Add(wall);}
}
#endregion

效果
在這里插入圖片描述

8、生成碰撞體

#region 碰撞體生成
private GameObject _collidersParent;void CreateCombinedCollider(List<Matrix4x4> matrices, Mesh mesh)
{if (mesh == null) return;if (_collidersParent == null){_collidersParent = new GameObject("Colliders");_collidersParent.transform.SetParent(transform);}// 合并所有網格CombineInstance[] combines = new CombineInstance[matrices.Count];for (int i = 0; i < matrices.Count; i++){combines[i].mesh = mesh;combines[i].transform = matrices[i];}Mesh combinedMesh = new Mesh();combinedMesh.CombineMeshes(combines);// 創建碰撞體父物體GameObject colliderGameObject = new GameObject(mesh.name + "Colliders");colliderGameObject.transform.SetParent(_collidersParent.transform);MeshCollider collider = colliderGameObject.AddComponent<MeshCollider>();collider.sharedMesh = combinedMesh;
}
#endregion

效果
在這里插入圖片描述

9、放置隨機物品

放置隨機物品就比較復雜了,不過我加了很多詳細的注釋,大家可以查看理解。

新增道具配置類

using UnityEngine;// 道具配置類
[System.Serializable]
public class DungeonProp
{public GameObject prefab; // 預制體public enum PositionType { Wall, Corner, Middle, Anywhere }// 放置位置類型public PositionType positionType; // 放置位置類型[Range(0, 1)] public float spawnProbability; // 生成概率(0~1)public int minCount; // 最小數量public int maxCount; // 最大數量public Vector3 offset; //放置偏移量public bool randomRotation; // 是否隨機旋轉
}

編寫道具放置系統邏輯,我這里使用了OBB (SAT) (有向包圍盒),精準的檢測物品是否相交,且保持良好的性能。

不了解什么是OBB的可以參考:【unity知識】unity使用AABB(軸對齊包圍盒)和OBB(定向包圍盒)優化碰撞檢測

#region 道具放置系統
[Header("道具放置系統")]
public DungeonProp[] dungeonProps; // 可放置的道具配置
private float _wallThickness; // 墻體一半厚度
private GameObject _propsParent; //父物體// 放置道具
void PlaceProps()
{if (dungeonProps == null || dungeonProps.Length == 0) return;_wallThickness = wallMesh.bounds.extents.z;// 計算實際可用區域(考慮墻體厚度)float usableWidth = roomSize.x - 2 * _wallThickness;float usableHeight = roomSize.y - 2 * _wallThickness;// 計算房間邊界(考慮墻體厚度)float minX = transform.position.x - usableWidth / 2;float maxX = transform.position.x + usableWidth / 2;float minZ = transform.position.z - usableHeight / 2;float maxZ = transform.position.z + usableHeight / 2;// 創建道具父物體if (_propsParent == null){_propsParent = new GameObject("PropsParent");_propsParent.transform.SetParent(transform);} // 存儲已放置物體的OBB信息List<OBB> placedOBBs = new List<OBB>();foreach (var prop in dungeonProps){// 計算實際要放置的數量int count = Random.Range(prop.minCount, prop.maxCount + 1);for (int i = 0; i < count; i++){// 根據概率決定是否生成if (Random.value > prop.spawnProbability) continue;Vector3 position = Vector3.zero;Quaternion rotation = Quaternion.identity;Vector3 halfExtents = Vector3.zero;// 根據位置類型確定位置和朝向switch (prop.positionType){case DungeonProp.PositionType.Wall:if (!TryFindWallPosition(prop.prefab, minX, maxX, minZ, maxZ,out position, out rotation, out halfExtents))continue;break;case DungeonProp.PositionType.Corner:if (!TryFindCornerPosition(prop.prefab, minX, maxX, minZ, maxZ,out position, out rotation, out halfExtents))continue;break;case DungeonProp.PositionType.Middle:if (!TryFindMiddlePosition(prop.prefab, minX, maxX, minZ, maxZ,out position, out rotation, out halfExtents))continue;break;case DungeonProp.PositionType.Anywhere:if (!TryFindAnyPosition(prop.prefab, minX, maxX, minZ, maxZ,out position, out rotation, out halfExtents))continue;break;}// 應用隨機旋轉if (prop.randomRotation){rotation = Quaternion.Euler(0, Random.Range(0, 360), 0);}// 檢查碰撞并放置道具if (!CheckOverlap(position, halfExtents, rotation, placedOBBs)){GameObject propInstance = Instantiate(prop.prefab, position + prop.offset, rotation);propInstance.transform.SetParent(_propsParent.transform);placedOBBs.Add(new OBB(){center = position,extents = halfExtents,rotation = rotation});}}}
}// 嘗試在墻邊放置道具
bool TryFindWallPosition(GameObject prefab, float minX, float maxX, float minZ, float maxZ,out Vector3 position, out Quaternion rotation, out Vector3 halfExtents)
{position = Vector3.zero;rotation = Quaternion.identity;halfExtents = Vector3.zero;// 獲取預制體大小Bounds bounds = GetPrefabBounds(prefab);if (bounds.size == Vector3.zero) return false;//獲取包圍盒(Bounds)的半尺寸halfExtents = bounds.extents;// 隨機選擇一面墻int wallIndex = Random.Range(0, 4);switch (wallIndex){case 0: // 北墻 (z最大)position = new Vector3(Random.Range(minX + halfExtents.x, maxX - halfExtents.x),0,maxZ - halfExtents.z);rotation = Quaternion.Euler(0, 180, 0); // 面朝南break;case 1: // 南墻 (z最小)position = new Vector3(Random.Range(minX + halfExtents.x, maxX - halfExtents.x),0,minZ + halfExtents.z);rotation = Quaternion.identity; // 面朝北break;case 2: // 東墻 (x最大)position = new Vector3(maxX - halfExtents.z,0,Random.Range(minZ + halfExtents.x, maxZ - halfExtents.x));rotation = Quaternion.Euler(0, 270, 0); // 面朝西break;case 3: // 西墻 (x最小)position = new Vector3(minX + halfExtents.z,0,Random.Range(minZ + halfExtents.x, maxZ - halfExtents.x));rotation = Quaternion.Euler(0, 90, 0); // 面朝東break;}return true;
}// 嘗試在角落放置道具
bool TryFindCornerPosition(GameObject prefab, float minX, float maxX, float minZ, float maxZ,out Vector3 position, out Quaternion rotation, out Vector3 halfExtents)
{position = Vector3.zero;rotation = Quaternion.identity;halfExtents = Vector3.zero;// 獲取預制體大小Bounds bounds = GetPrefabBounds(prefab);if (bounds.size == Vector3.zero) return false;//獲取包圍盒(Bounds)的半尺寸halfExtents = bounds.extents;// 隨機選擇一個墻角int cornerIndex = Random.Range(0, 4);float cornerOffset = _wallThickness + Mathf.Max(halfExtents.x, halfExtents.z);switch (cornerIndex){case 0: // 西北角position = new Vector3(minX + cornerOffset,0,maxZ - cornerOffset);rotation = Quaternion.Euler(0, 225, 0); // 面向東南break;case 1: // 東北角position = new Vector3(maxX - cornerOffset,0,maxZ - cornerOffset);rotation = Quaternion.Euler(0, 315, 0); // 面向西南break;case 2: // 西南角position = new Vector3(minX + cornerOffset,0,minZ + cornerOffset);rotation = Quaternion.Euler(0, 135, 0); // 面向東北break;case 3: // 東南角position = new Vector3(maxX - cornerOffset,0,minZ + cornerOffset);rotation = Quaternion.Euler(0, 45, 0); // 面向西北break;}return true;
}// 嘗試在中間區域放置道具
bool TryFindMiddlePosition(GameObject prefab, float minX, float maxX, float minZ, float maxZ,out Vector3 position, out Quaternion rotation, out Vector3 halfExtents)
{position = Vector3.zero;rotation = Quaternion.identity;halfExtents = Vector3.zero;// 獲取預制體大小Bounds bounds = GetPrefabBounds(prefab);if (bounds.size == Vector3.zero) return false;//獲取包圍盒(Bounds)的半尺寸halfExtents = bounds.extents;// 中間區域(避開墻邊)float safeMargin = Mathf.Max(halfExtents.x, halfExtents.z) * 2;position = new Vector3(Random.Range(minX + safeMargin, maxX - safeMargin),0,Random.Range(minZ + safeMargin, maxZ - safeMargin));return true;
}// 嘗試在任意位置放置道具
bool TryFindAnyPosition(GameObject prefab, float minX, float maxX, float minZ, float maxZ,out Vector3 position, out Quaternion rotation, out Vector3 halfExtents)
{// 50%概率放在墻邊,25%概率放在墻角,25%概率放在中間float rand = Random.value;if (rand < 0.5f)return TryFindWallPosition(prefab, minX, maxX, minZ, maxZ, out position, out rotation, out halfExtents);else if (rand < 0.75f)return TryFindCornerPosition(prefab, minX, maxX, minZ, maxZ, out position, out rotation, out halfExtents);elsereturn TryFindMiddlePosition(prefab, minX, maxX, minZ, maxZ, out position, out rotation, out halfExtents);
}// 獲取預制體的包圍盒
Bounds GetPrefabBounds(GameObject prefab)
{Renderer renderer = prefab.GetComponentInChildren<Renderer>();if (renderer != null) return renderer.bounds;// 如果預制體沒有渲染器,嘗試使用碰撞器Collider collider = prefab.GetComponentInChildren<Collider>();if (collider != null) return collider.bounds;// 默認大小return new Bounds(Vector3.zero, Vector3.one * 0.5f);
}/// <summary>
/// 檢查新物體是否與已放置物體發生OBB重疊
/// </summary>
/// <param name="position">新物體的中心位置</param>
/// <param name="halfExtents">新物體的半尺寸</param>
/// <param name="rotation">新物體的旋轉</param>
/// <param name="existingOBBs">已放置物體的OBB列表</param>
/// <returns>true表示有重疊,false表示無重疊</returns>
bool CheckOverlap(Vector3 position, Vector3 halfExtents, Quaternion rotation, List<OBB> existingOBBs)
{// 創建新物體的OBBOBB newOBB = new OBB(){center = position,    // 設置中心點extents = halfExtents, // 設置半尺寸rotation = rotation   // 設置旋轉};// 遍歷所有已放置物體的OBBforeach (var obb in existingOBBs){// 如果與任一已放置物體相交,返回trueif (newOBB.Intersects(obb))return true;}// 沒有發現重疊return false;
}
#endregion
}

配置數據
在這里插入圖片描述
效果,實現在不同位置,按概率生成不同物品
在這里插入圖片描述

10、設置隨機種子

Random.InitState 是 Unity 引擎中用于初始化隨機數生成器的方法,它的作用是設定隨機數生成的種子(Seed),從而控制隨機序列的起始點。它的核心作用是:

  • 確定性隨機:使用相同的種子時,Random 產生的隨機數序列會完全一致。

    Random.InitState(123); // 初始化種子為123
    Debug.Log(Random.Range(0, 100)); // 固定輸出某個值(如42)
    

    每次運行程序,只要種子是 123,第一個 Random.Range(0, 100) 必定返回相同的值。

  • 取消真正的隨機性:默認情況下,Unity 使用系統時間作為種子,結果不可預測。而 InitState 會覆蓋這一行為,使隨機結果可重現。

所以我們可以給我們的項目添加隨機種子功能

[Header("隨機種子")]
[SerializeField] private int _seed; // 隨機種子
[SerializeField] private bool _useSeed; // 是否使用指定種子void Start()
{// 初始化隨機種子if (_useSeed){Random.InitState(_seed); // 使用指定的種子}else{int randomSeed = Random.Range(1, 1000000); // 生成隨機種子Random.InitState(randomSeed);Debug.Log(randomSeed); // 輸出種子以便重現}//...
}

我們使用相同的種子,一直會生成相同的地牢

源碼

https://gitee.com/unity_data/unity6-urpgeneration-dungeon
在這里插入圖片描述

參考

https://www.youtube.com/watch?v=PhLcNhK9aro


專欄推薦

地址
【unity游戲開發入門到精通——C#篇】
【unity游戲開發入門到精通——unity通用篇】
【unity游戲開發入門到精通——unity3D篇】
【unity游戲開發入門到精通——unity2D篇】
【unity實戰】
【制作100個Unity游戲】
【推薦100個unity插件】
【實現100個unity特效】
【unity框架/工具集開發】
【unity游戲開發——模型篇】
【unity游戲開發——InputSystem】
【unity游戲開發——Animator動畫】
【unity游戲開發——UGUI】
【unity游戲開發——聯網篇】
【unity游戲開發——優化篇】
【unity游戲開發——shader篇】
【unity游戲開發——編輯器擴展】
【unity游戲開發——熱更新】
【unity游戲開發——網絡】

完結

好了,我是向宇,博客地址:https://xiangyu.blog.csdn.net,如果學習過程中遇到任何問題,也歡迎你評論私信找我。

贈人玫瑰,手有余香!如果文章內容對你有所幫助,請不要吝嗇你的點贊評論和關注,你的每一次支持都是我不斷創作的最大動力。當然如果你發現了文章中存在錯誤或者有更好的解決方法,也歡迎評論私信告訴我哦!
在這里插入圖片描述

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/917954.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/917954.shtml
英文地址,請注明出處:http://en.pswp.cn/news/917954.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

多賬號管理方案:解析一款免Root的App分身工具

之前有小伙伴問阿燦有沒有可以軟件分身的免費軟件&#xff0c;后來阿燦找到了一款可以無限分身的app感覺很實用&#xff0c;只有10M大小 02軟件介紹說白了它能給各種app和游戲做分身&#xff0c;包括V信、qQ、某音、某付寶這些&#xff0c;而且支持最新的安卓15系統。每個分身…

(附源碼)基于PHP和Vue的網上購物平臺

內容摘要 內容摘要: 隨著互聯網技術的迅猛發展&#xff0c;網上購物已成為人們日常生活的重要組成部分。本文圍繞PHPVue技術棧構建的網上購物平臺展開研究&#xff0c;深入探討了該平臺的架構設計與實現細節。平臺前端采用Vue框架&#xff0c;利用其組件化開發和數據驅動的特性…

51單片機

中斷系統1.什么是中斷當CPU正在處理某件事的時候外界發生了緊急事件請求&#xff0c;要求CPU暫停當前的工作&#xff0c;轉而去處理這個緊急事件&#xff0c;處理完以后&#xff0c;再回到原來被中斷的地方&#xff0c;繼續原來的工作&#xff0c;這樣的過程稱為中斷2.為什么要…

前端開發:HTML(5)—— 表單

下面我們來學習表單。 目錄 什么是Web表單&#xff1f; 表單標簽 1.form標簽 2.輸入框 文本框和密碼框 單選框和復選框 1.單選框 2.復選框 3.按鈕 &#xff08;1&#xff09;普通按鈕 &#xff08;2&#xff09;提交按鈕 &#xff08;3&#xff09;重置按鈕 &#…

【YOLOv8改進 - C2f融合】C2f融合SFS-Conv(空間 - 頻率選擇卷積)提升特征多樣性,同時減少參數和計算量

YOLOv8目標檢測創新改進與實戰案例專欄 專欄目錄: YOLOv8有效改進系列及項目實戰目錄 包含卷積,主干 注意力,檢測頭等創新機制 以及 各種目標檢測分割項目實戰案例 專欄鏈接: YOLOv8基礎解析+創新改進+實戰案例 文章目錄 YOLOv8目標檢測創新改進與實戰案例專欄 介紹 摘要 文…

如何將照片從POCO手機傳輸到Mac電腦

將照片從POCO手機傳輸到Mac電腦可能會有些困難&#xff0c;因為與iPhone不同&#xff0c;POCO設備沒有原生的macOS支持。這常常讓用戶尋找簡單、有效的方法來移動圖片&#xff0c;同時避免丟失質量&#xff0c;節省時間&#xff0c;并避免復雜的軟件設置。如果你想知道如何將照…

最新教程 | CentOS 7 內網環境 Nginx + ECharts 頁面離線部署手冊(RPM 安裝方式)

&#x1f4c1; 一、準備階段&#xff08;在聯網電腦上完成&#xff09; 1.1 下載 Nginx 官方 RPM 安裝包 在聯網電腦瀏覽器中訪問 Nginx 官方穩定版本倉庫&#xff1a; &#x1f517; 地址&#xff1a;http://nginx.org/packages/centos/7/x86_64/ ??云盤&#xff1a;htt…

Redis 常用數據類型 (下)

文章目錄前言一 Hash 哈希1. Hash 相關命令hset 和 hgethexistshdelhkeyshvalshgetallhmgethlenhsetnxhincrbyincrbyfloat2. Hash 命令小結3. Hash 內部編碼Hash 在緩存中的應用場景介紹緩存方式對比二、List 列表1. LIST總體介紹2. List 普通命令lpushlpushxrpushrpushxlrange…

Java Lambda表達式:簡潔高效的函數式編程

1 lambda表達式Lambda 表達式本質是一個匿名函數&#xff0c;用于把函數作為參數&#xff0c;傳入方法中&#xff0c;實現函數式編程風格。使用Lambda 表達式可以使代碼變的更加簡潔緊湊。語法格式&#xff1a;(parameters)-> expression 或 (parameters)->{ statements…

python中的集合

目錄 初識集合 集合的含義 集合的作用 集合的使用場景 集合的定義 集合的常用操作 元素的增加 函數add() 元素的刪除 函數remove() 函數clear() 函數pop() 集合的遍歷 for循環 while循環 初識集合 集合的含義 在pyrhon中&#xff0c;集合是一種內置的數據結構…

如何將普通HTTP API接口改造為MCP服務器

在現代微服務架構中&#xff0c;MCP&#xff08;Mesh Configuration Protocol&#xff09; 作為高效配置分發協議&#xff0c;正逐漸替代傳統HTTP API。本文將手把手教你如何將普通HTTP API升級為高性能MCP服務器。 為什么需要MCP&#xff1f; 傳統HTTP API在配置分發場景存在…

數據結構第8問:什么是樹?

樹 【本節僅描述樹的定義、術語以及相關性質】 定義 樹是由若干個結點組成的有限集合。具有如下特征&#xff1a; 有且僅有一個根結點&#xff1b;除根結點外&#xff0c;每個其它結點有且僅有一個直接的父結點&#xff1b;除根結點外&#xff0c;每個結點可以有零個或者多個子…

PyTorch RNN 名字分類器

PyTorch RNN 名字分類器詳解 使用PyTorch實現的字符級RNN&#xff08;循環神經網絡&#xff09;項目&#xff0c;用于根據人名預測其所屬的語言/國家。該模型通過學習不同語言名字的字符模式&#xff0c;夠識別名字的語言起源。 環境設置 import torch import string import un…

面向對象之類方法,成員變量和局部變量

1.類的方法必須包含幾個部分&#xff1f;2.成員變量和局部變量類的方法必須包含哪幾個部分&#xff1f;.方法名&#xff1a;用于標識方法的名稱&#xff0c;遵循標識符命名規則&#xff0c;通常采用駝峰命名法。返回值類型&#xff1a;指定方法返回的數據類型。如果方法不返回任…

古法筆記 | 通過查表進行ASCII字符編碼轉換

ASCII字符集是比較早期的一種字符編碼&#xff0c;只能表示英文字符&#xff0c;最多能表示128個字符。 字符集規定了每個字符和二進制數之間的對應關系&#xff0c;可以通過查表完成二進制數到字符的轉換ASCII字符占用的存儲空間是定長的1字節 ASCII字符的官方碼點表見下圖&…

Linux C實現單生產者多消費者環形緩沖區

使用C11里的原子變量實現&#xff0c;沒有用互斥鎖&#xff0c;效率更高。ring_buffer.h:/*** file ring_buffer.h* author tl* brief 單生產者多消費者環形緩沖區&#xff0c;每條數據被所有消費者讀后才釋放。讀線程安全&#xff0c;寫僅單線程。* version* date 2025-08-06*…

復雜場景識別率↑31%!陌訊多模態融合算法在智慧環衛的實戰解析

摘要&#xff1a;針對邊緣計算優化的垃圾堆放識別場景&#xff0c;本文解析了基于動態決策機制的視覺算法如何提升復雜環境的魯棒性。實測數據顯示在遮擋/光照干擾下&#xff0c;mAP0.5較基線提升28.3%&#xff0c;誤報率降低至行業1/5水平。一、行業痛點&#xff1a;智慧環衛的…

MyBatis-Plus Service 接口:如何在 MyBatis-Plus 中實現業務邏輯層??

全文目錄&#xff1a;開篇語前言1. MyBatis-Plus 的 IService 接口1.1 基本使用示例&#xff1a;創建實體類 User 和 UserService1.2 創建 IService 接口1.3 創建 ServiceImpl 類1.4 典型的數據庫操作方法1.4.1 save()&#xff1a;保存數據1.4.2 remove()&#xff1a;刪除數據1…

[激光原理與應用-168]:光源 - 常見光源的分類、特性及應用場景的詳細解析,涵蓋技術原理、優缺點及典型應用領域

一、半導體光源1. LED光源&#xff08;發光二極管&#xff09;原理&#xff1a;通過半導體PN結的電子-空穴復合發光&#xff0c;波長由材料帶隙決定&#xff08;如GaN發藍光、AlGaInP發紅光&#xff09;。特性&#xff1a;優點&#xff1a;壽命長&#xff08;>5萬小時&#…

Metronic v.7.1.7企業級Web應用前端框架全攻略

本文還有配套的精品資源&#xff0c;點擊獲取 簡介&#xff1a;Metronic是一款專注于構建響應式、高性能企業級Web應用的前端開發框架。最新版本v.7.1.7引入了多種功能和優化&#xff0c;以增強開發效率和用戶體驗。詳細介紹了其核心特性&#xff0c;包括響應式設計、多種模…