[Add] TextMeshPro & Chunk maker

This commit is contained in:
2026-03-30 06:16:25 +07:00
parent e2dab2208a
commit 0b06ff5a06
103 changed files with 70513 additions and 67 deletions
@@ -35,6 +35,10 @@ namespace InfiniteWorld
[SerializeField] private float environmentNoiseScale = 0.19f;
[SerializeField] private float environmentThreshold = 0.7f;
[Header("Random Objects")]
[SerializeField, Range(0f, 1f)] private float randomPrefabChance = 0.06f;
[SerializeField] private float randomPrefabZOffset = -0.1f;
[Header("Streaming")]
[SerializeField, Min(1)] private int maxAsyncChunkJobs = 2;
[SerializeField, Min(1)] private int maxChunkRendersPerFrame = 1;
@@ -92,6 +96,36 @@ namespace InfiniteWorld
SetLoadingPaused(false);
}
public bool UsesProfile(WorldAutotileProfile candidateProfile)
{
return profile == candidateProfile;
}
public void EditorRefreshFromProfile()
{
EnsureSceneInfrastructure();
EnsureRuntimeData();
cachedGroundTile = null;
cachedGroundTiles = null;
List<Vector2Int> coords = new List<Vector2Int>(chunks.Keys);
for (int i = 0; i < coords.Count; i++)
{
if (!chunks.TryGetValue(coords[i], out ChunkRuntime runtime) || !runtime.HasData)
{
continue;
}
runtime.State = ChunkState.ReadyToRender;
RenderChunk(coords[i]);
}
for (int i = 0; i < coords.Count; i++)
{
RefreshNeighborBorders(coords[i]);
}
}
private void EnsureSceneInfrastructure()
{
grid = GetComponentInChildren<Grid>();
@@ -120,7 +154,10 @@ namespace InfiniteWorld
if (profile == null || !profile.HasAnyAssignedTiles())
{
runtimeFallbackProfile = RuntimeWorldProfileFactory.CreateFallbackProfile();
return;
}
runtimeFallbackProfile = null;
}
private bool TryFindPlayer()
@@ -215,8 +252,10 @@ namespace InfiniteWorld
Tilemap ground = CreateTilemap("Ground", chunkObject.transform, 0, false);
Tilemap walls = CreateTilemap("Walls", chunkObject.transform, 1, true);
Tilemap environment = CreateTilemap("Environment", chunkObject.transform, 2, false);
Transform objectsRoot = new GameObject("Objects").transform;
objectsRoot.SetParent(chunkObject.transform, false);
return new GeneratedChunk(chunkObject.transform, ground, walls, environment);
return new GeneratedChunk(chunkObject.transform, ground, walls, environment, objectsRoot);
}
private Tilemap CreateTilemap(string name, Transform parent, int sortingOrder, bool addCollision)
@@ -473,6 +512,7 @@ namespace InfiniteWorld
chunk.Ground.SetTilesBlock(bounds, groundTiles);
chunk.Walls.SetTilesBlock(bounds, wallTiles);
chunk.Environment.SetTilesBlock(bounds, environmentTiles);
RenderRandomPrefabs(coord, runtime, activeProfile);
runtime.State = ChunkState.Rendered;
}
@@ -620,6 +660,102 @@ namespace InfiniteWorld
return activeProfile.environmentTiles[0].tile;
}
private void RenderRandomPrefabs(Vector2Int coord, ChunkRuntime runtime, WorldAutotileProfile activeProfile)
{
if (runtime.Chunk.ObjectsRoot == null)
{
return;
}
ClearSpawnedObjects(runtime.Chunk.ObjectsRoot);
if (randomPrefabChance <= 0f || activeProfile.randomPrefabs == null || activeProfile.randomPrefabs.Count == 0)
{
return;
}
for (int y = 0; y < chunkSize; y++)
{
for (int x = 0; x < chunkSize; x++)
{
if (runtime.WallMask[x, y] || runtime.EnvironmentMask[x, y])
{
continue;
}
Vector2Int worldCell = ChunkToWorldCell(coord, x, y);
if (Hash01(worldCell.x, worldCell.y, seed + 1103) > randomPrefabChance)
{
continue;
}
GameObject prefab = PickRandomPrefab(worldCell, activeProfile);
if (prefab == null)
{
continue;
}
GameObject instance = Instantiate(prefab, runtime.Chunk.ObjectsRoot);
instance.transform.localPosition = new Vector3(x + 0.5f, y + 0.5f, randomPrefabZOffset);
instance.transform.localRotation = Quaternion.identity;
instance.name = prefab.name;
}
}
}
private GameObject PickRandomPrefab(Vector2Int worldCell, WorldAutotileProfile activeProfile)
{
float total = 0f;
for (int i = 0; i < activeProfile.randomPrefabs.Count; i++)
{
RandomPrefabEntry entry = activeProfile.randomPrefabs[i];
if (entry != null && entry.prefab != null)
{
total += Mathf.Max(0.01f, entry.weight);
}
}
if (total <= 0f)
{
return null;
}
float selector = Hash01(worldCell.x, worldCell.y, seed + 1409) * total;
float cumulative = 0f;
for (int i = 0; i < activeProfile.randomPrefabs.Count; i++)
{
RandomPrefabEntry entry = activeProfile.randomPrefabs[i];
if (entry == null || entry.prefab == null)
{
continue;
}
cumulative += Mathf.Max(0.01f, entry.weight);
if (selector <= cumulative)
{
return entry.prefab;
}
}
for (int i = 0; i < activeProfile.randomPrefabs.Count; i++)
{
RandomPrefabEntry entry = activeProfile.randomPrefabs[i];
if (entry != null && entry.prefab != null)
{
return entry.prefab;
}
}
return null;
}
private static void ClearSpawnedObjects(Transform root)
{
for (int i = root.childCount - 1; i >= 0; i--)
{
UnityEngine.Object.Destroy(root.GetChild(i).gameObject);
}
}
private Vector2Int WorldToChunk(Vector3 position)
{
return new Vector2Int(
@@ -872,18 +1008,20 @@ namespace InfiniteWorld
private readonly struct GeneratedChunk
{
public GeneratedChunk(Transform root, Tilemap ground, Tilemap walls, Tilemap environment)
public GeneratedChunk(Transform root, Tilemap ground, Tilemap walls, Tilemap environment, Transform objectsRoot)
{
Root = root;
Ground = ground;
Walls = walls;
Environment = environment;
ObjectsRoot = objectsRoot;
}
public Transform Root { get; }
public Tilemap Ground { get; }
public Tilemap Walls { get; }
public Tilemap Environment { get; }
public Transform ObjectsRoot { get; }
}
private sealed class ChunkRuntime
@@ -8,9 +8,12 @@ namespace InfiniteWorld
[CreateAssetMenu(menuName = "Infinite World/World Autotile Profile", fileName = "WorldAutotileProfile")]
public class WorldAutotileProfile : ScriptableObject
{
public bool autoUpdatePaletteLayout = true;
public bool autoRefreshGeneratedWorld = true;
public TileBase baseGroundTile;
public AutoTileDefinition wallTiles = new AutoTileDefinition();
public List<EnvironmentTileEntry> environmentTiles = new List<EnvironmentTileEntry>();
public List<RandomPrefabEntry> randomPrefabs = new List<RandomPrefabEntry>();
public TileBase GetWallTile(AutoTileShape shape)
{
@@ -29,11 +32,14 @@ namespace InfiniteWorld
return true;
}
for (int i = 0; i < environmentTiles.Count; i++)
if (environmentTiles != null)
{
if (environmentTiles[i] != null && environmentTiles[i].tile != null)
for (int i = 0; i < environmentTiles.Count; i++)
{
return true;
if (environmentTiles[i] != null && environmentTiles[i].tile != null)
{
return true;
}
}
}
@@ -98,6 +104,27 @@ namespace InfiniteWorld
return tile ?? center;
}
public TileBase GetAssignedTile(AutoTileShape shape)
{
return shape switch
{
AutoTileShape.Center => center,
AutoTileShape.Top => top,
AutoTileShape.Right => right,
AutoTileShape.Bottom => bottom,
AutoTileShape.Left => left,
AutoTileShape.OuterTopLeft => outerTopLeft,
AutoTileShape.OuterTopRight => outerTopRight,
AutoTileShape.OuterBottomRight => outerBottomRight,
AutoTileShape.OuterBottomLeft => outerBottomLeft,
AutoTileShape.InnerTopLeft => innerTopLeft,
AutoTileShape.InnerTopRight => innerTopRight,
AutoTileShape.InnerBottomRight => innerBottomRight,
AutoTileShape.InnerBottomLeft => innerBottomLeft,
_ => null
};
}
public bool HasAnyAssignedTiles()
{
return center != null ||
@@ -123,4 +150,12 @@ namespace InfiniteWorld
public TileBase tile;
[Min(0f)] public float weight = 1f;
}
[Serializable]
public class RandomPrefabEntry
{
public string id = "RandomPrefab";
public GameObject prefab;
[Min(0f)] public float weight = 1f;
}
}