[Fix] Flatten Terrain

This commit is contained in:
2026-04-08 09:41:17 +07:00
parent 50831947b9
commit 211c975889
3 changed files with 241 additions and 0 deletions
@@ -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();