Добавить детерминированное размещение dungeon prefab в voxel-мире через stamp/carve в данных чанков. #6
@@ -0,0 +1,232 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace InfiniteWorld.VoxelWorld
|
||||
{
|
||||
public sealed partial class VoxelWorldGenerator
|
||||
{
|
||||
private readonly Dictionary<Vector2Int, List<SpawnedPlacementRuntime>> spawnedPlacementsByChunk = new Dictionary<Vector2Int, List<SpawnedPlacementRuntime>>();
|
||||
private readonly Dictionary<long, SpawnedPlacementRuntime> spawnedPlacementsById = new Dictionary<long, SpawnedPlacementRuntime>();
|
||||
private readonly HashSet<long> disabledSpawnIds = new HashSet<long>();
|
||||
|
||||
private Transform placementRoot;
|
||||
|
||||
public IReadOnlyCollection<long> DisabledSpawnIds => disabledSpawnIds;
|
||||
|
||||
public bool IsSpawnDisabled(long spawnId)
|
||||
{
|
||||
return disabledSpawnIds.Contains(spawnId);
|
||||
}
|
||||
|
||||
public bool DisableSpawn(long spawnId)
|
||||
{
|
||||
if (!disabledSpawnIds.Add(spawnId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (spawnedPlacementsById.TryGetValue(spawnId, out SpawnedPlacementRuntime runtime))
|
||||
{
|
||||
RemoveSpawnedPlacement(runtime);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool EnableSpawn(long spawnId)
|
||||
{
|
||||
return disabledSpawnIds.Remove(spawnId);
|
||||
}
|
||||
|
||||
public void ClearDisabledSpawns()
|
||||
{
|
||||
disabledSpawnIds.Clear();
|
||||
}
|
||||
|
||||
private void EnsurePlacementRoot()
|
||||
{
|
||||
if (placementRoot != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Transform existing = transform.Find("WorldPlacements");
|
||||
if (existing != null)
|
||||
{
|
||||
placementRoot = existing;
|
||||
return;
|
||||
}
|
||||
|
||||
GameObject root = new GameObject("WorldPlacements");
|
||||
root.transform.SetParent(transform, false);
|
||||
placementRoot = root.transform;
|
||||
}
|
||||
|
||||
private void TrySpawnChunkPlacements(Vector2Int chunkCoord)
|
||||
{
|
||||
EnsurePlacementRoot();
|
||||
if (placementRoot == null || spawnedPlacementsByChunk.ContainsKey(chunkCoord))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
WorldChunkPlacementPlan plan = GetChunkPlacementPlan(chunkCoord);
|
||||
IReadOnlyList<WorldSpawnPoint> spawnPoints = plan.SpawnPoints;
|
||||
if (spawnPoints == null || spawnPoints.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
List<SpawnedPlacementRuntime> chunkPlacements = new List<SpawnedPlacementRuntime>(spawnPoints.Count);
|
||||
for (int i = 0; i < spawnPoints.Count; i++)
|
||||
{
|
||||
WorldSpawnPoint spawnPoint = spawnPoints[i];
|
||||
if (disabledSpawnIds.Contains(spawnPoint.SpawnId) || spawnedPlacementsById.ContainsKey(spawnPoint.SpawnId))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!TryResolveSpawnPrefab(spawnPoint, out GameObject prefab) || prefab == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
GameObject instance = Object.Instantiate(prefab, spawnPoint.Position, spawnPoint.Rotation, placementRoot);
|
||||
instance.name = $"{prefab.name}_{spawnPoint.ChunkCoord.x}_{spawnPoint.ChunkCoord.y}_{spawnPoint.SpawnOrdinalInChunk}";
|
||||
SpawnedPlacementRuntime runtime = new SpawnedPlacementRuntime(spawnPoint.SpawnId, chunkCoord, spawnPoint.CollectionIndex, spawnPoint.EntryIndex, instance);
|
||||
chunkPlacements.Add(runtime);
|
||||
spawnedPlacementsById.Add(runtime.SpawnId, runtime);
|
||||
}
|
||||
|
||||
if (chunkPlacements.Count > 0)
|
||||
{
|
||||
spawnedPlacementsByChunk[chunkCoord] = chunkPlacements;
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryResolveSpawnPrefab(WorldSpawnPoint spawnPoint, out GameObject prefab)
|
||||
{
|
||||
prefab = null;
|
||||
if (config == null || config.placementCollections == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (spawnPoint.CollectionIndex < 0 || spawnPoint.CollectionIndex >= config.placementCollections.Count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
WorldPrefabCollection collection = config.placementCollections[spawnPoint.CollectionIndex];
|
||||
if (collection == null || collection.entries == null || spawnPoint.EntryIndex < 0 || spawnPoint.EntryIndex >= collection.entries.Count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
WorldPrefabEntry entry = collection.entries[spawnPoint.EntryIndex];
|
||||
if (entry == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
prefab = entry.prefab;
|
||||
return prefab != null;
|
||||
}
|
||||
|
||||
private void DespawnChunkPlacements(Vector2Int chunkCoord)
|
||||
{
|
||||
if (!spawnedPlacementsByChunk.TryGetValue(chunkCoord, out List<SpawnedPlacementRuntime> chunkPlacements))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < chunkPlacements.Count; i++)
|
||||
{
|
||||
SpawnedPlacementRuntime runtime = chunkPlacements[i];
|
||||
spawnedPlacementsById.Remove(runtime.SpawnId);
|
||||
DestroyPlacementInstance(runtime.Instance);
|
||||
}
|
||||
|
||||
spawnedPlacementsByChunk.Remove(chunkCoord);
|
||||
}
|
||||
|
||||
private void RemoveSpawnedPlacement(SpawnedPlacementRuntime runtime)
|
||||
{
|
||||
spawnedPlacementsById.Remove(runtime.SpawnId);
|
||||
if (spawnedPlacementsByChunk.TryGetValue(runtime.ChunkCoord, out List<SpawnedPlacementRuntime> chunkPlacements))
|
||||
{
|
||||
chunkPlacements.RemoveAll(item => item.SpawnId == runtime.SpawnId);
|
||||
if (chunkPlacements.Count == 0)
|
||||
{
|
||||
spawnedPlacementsByChunk.Remove(runtime.ChunkCoord);
|
||||
}
|
||||
}
|
||||
|
||||
DestroyPlacementInstance(runtime.Instance);
|
||||
}
|
||||
|
||||
private void CleanupSpawnedPlacements()
|
||||
{
|
||||
foreach (KeyValuePair<Vector2Int, List<SpawnedPlacementRuntime>> pair in spawnedPlacementsByChunk)
|
||||
{
|
||||
List<SpawnedPlacementRuntime> chunkPlacements = pair.Value;
|
||||
for (int i = 0; i < chunkPlacements.Count; i++)
|
||||
{
|
||||
DestroyPlacementInstance(chunkPlacements[i].Instance);
|
||||
}
|
||||
}
|
||||
|
||||
spawnedPlacementsByChunk.Clear();
|
||||
spawnedPlacementsById.Clear();
|
||||
|
||||
if (placementRoot != null)
|
||||
{
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
Object.Destroy(placementRoot.gameObject);
|
||||
}
|
||||
else
|
||||
{
|
||||
Object.DestroyImmediate(placementRoot.gameObject);
|
||||
}
|
||||
|
||||
placementRoot = null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void DestroyPlacementInstance(GameObject instance)
|
||||
{
|
||||
if (instance == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
Object.Destroy(instance);
|
||||
}
|
||||
else
|
||||
{
|
||||
Object.DestroyImmediate(instance);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class SpawnedPlacementRuntime
|
||||
{
|
||||
public SpawnedPlacementRuntime(long spawnId, Vector2Int chunkCoord, int collectionIndex, int entryIndex, GameObject instance)
|
||||
{
|
||||
SpawnId = spawnId;
|
||||
ChunkCoord = chunkCoord;
|
||||
CollectionIndex = collectionIndex;
|
||||
EntryIndex = entryIndex;
|
||||
Instance = instance;
|
||||
}
|
||||
|
||||
public long SpawnId { get; }
|
||||
public Vector2Int ChunkCoord { get; }
|
||||
public int CollectionIndex { get; }
|
||||
public int EntryIndex { get; }
|
||||
public GameObject Instance { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f7d0d16ba43223c41bd21fbe9333d819
|
||||
@@ -75,6 +75,7 @@ namespace InfiniteWorld.VoxelWorld
|
||||
EnsureRuntimeData();
|
||||
EnsureChunkRoot();
|
||||
EnsureRegionRoot();
|
||||
EnsurePlacementRoot();
|
||||
TryResolveStreamTarget();
|
||||
}
|
||||
|
||||
@@ -83,6 +84,7 @@ namespace InfiniteWorld.VoxelWorld
|
||||
EnsureRuntimeData();
|
||||
EnsureChunkRoot();
|
||||
EnsureRegionRoot();
|
||||
EnsurePlacementRoot();
|
||||
if (!TryResolveStreamTarget())
|
||||
{
|
||||
return;
|
||||
@@ -121,6 +123,7 @@ namespace InfiniteWorld.VoxelWorld
|
||||
CleanupChunks();
|
||||
CleanupRegions();
|
||||
CleanupPlacementPlans();
|
||||
CleanupSpawnedPlacements();
|
||||
atlas?.Dispose();
|
||||
atlas = null;
|
||||
}
|
||||
@@ -290,6 +293,7 @@ namespace InfiniteWorld.VoxelWorld
|
||||
{
|
||||
chunkPlacementPlans.Remove(coord);
|
||||
}
|
||||
DespawnChunkPlacements(coord);
|
||||
runtime.Dispose();
|
||||
TryDisposeRegionIfEmpty(regionCoord);
|
||||
QueueNeighborRefresh(coord);
|
||||
@@ -804,6 +808,8 @@ namespace InfiniteWorld.VoxelWorld
|
||||
runtime.State = ChunkState.ReadyToRender;
|
||||
}
|
||||
|
||||
TrySpawnChunkPlacements(result.Coord);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -890,6 +896,7 @@ namespace InfiniteWorld.VoxelWorld
|
||||
}
|
||||
|
||||
chunks.Clear();
|
||||
CleanupSpawnedPlacements();
|
||||
dirtyChunkMeshes.Clear();
|
||||
queuedChunkMeshes.Clear();
|
||||
pendingNeighborRefreshes.Clear();
|
||||
|
||||
Reference in New Issue
Block a user