Move Files, remove 2D world gen

This commit is contained in:
2026-04-07 03:10:03 +07:00
parent 55cea836ed
commit 9675b7b31d
121 changed files with 77 additions and 6922 deletions
-8
View File
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: ff30f393052d2674e94fd1d887712090
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
-31
View File
@@ -1,31 +0,0 @@
using UnityEngine;
namespace InfiniteWorld
{
public class CameraFollow2D : MonoBehaviour
{
[SerializeField] private Transform target;
[SerializeField] private float smoothTime = 0.18f;
[SerializeField] private Vector3 offset = new Vector3(0f, 0f, -10f);
private Vector3 velocity;
private void LateUpdate()
{
if (target == null)
{
SimplePlayerInputMover player = FindFirstObjectByType<SimplePlayerInputMover>();
if (player == null)
{
return;
}
target = player.transform;
}
Vector3 desiredPosition = target.position + offset;
desiredPosition.z = offset.z;
transform.position = Vector3.SmoothDamp(transform.position, desiredPosition, ref velocity, smoothTime);
}
}
}
@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 98ee4fb5b3ebf80478e6e25afa8fd337
@@ -1,109 +0,0 @@
using UnityEngine;
using UnityEngine.InputSystem;
namespace InfiniteWorld
{
public class SimplePlayerInputMover : MonoBehaviour
{
[SerializeField] private float moveSpeed = 5f;
private InputAction moveAction;
private Rigidbody2D rb;
private Vector2 moveInput;
private void Awake()
{
rb = GetComponent<Rigidbody2D>();
if (rb == null)
{
rb = gameObject.AddComponent<Rigidbody2D>();
}
ConfigurePhysics();
EnsureVisual();
moveAction = new InputAction("Move", InputActionType.Value);
moveAction.AddCompositeBinding("2DVector")
.With("Up", "<Keyboard>/w")
.With("Down", "<Keyboard>/s")
.With("Left", "<Keyboard>/a")
.With("Right", "<Keyboard>/d");
moveAction.AddCompositeBinding("2DVector")
.With("Up", "<Keyboard>/upArrow")
.With("Down", "<Keyboard>/downArrow")
.With("Left", "<Keyboard>/leftArrow")
.With("Right", "<Keyboard>/rightArrow");
moveAction.AddBinding("<Gamepad>/leftStick");
}
private void OnEnable()
{
moveAction?.Enable();
}
private void OnDisable()
{
moveAction?.Disable();
}
private void OnDestroy()
{
moveAction?.Dispose();
}
private void Update()
{
if (moveAction == null)
{
return;
}
moveInput = moveAction.ReadValue<Vector2>().normalized;
if (moveInput.x != 0f)
{
Vector3 scale = transform.localScale;
scale.x = Mathf.Abs(scale.x) * Mathf.Sign(moveInput.x);
transform.localScale = scale;
}
}
private void FixedUpdate()
{
if (rb == null)
{
return;
}
Vector2 target = rb.position + moveInput * (moveSpeed * Time.fixedDeltaTime);
rb.MovePosition(target);
}
private void ConfigurePhysics()
{
rb.gravityScale = 0f;
rb.freezeRotation = true;
rb.interpolation = RigidbodyInterpolation2D.Interpolate;
rb.collisionDetectionMode = CollisionDetectionMode2D.Continuous;
CapsuleCollider2D collider = GetComponent<CapsuleCollider2D>();
if (collider == null)
{
collider = gameObject.AddComponent<CapsuleCollider2D>();
}
collider.direction = CapsuleDirection2D.Vertical;
collider.size = new Vector2(0.55f, 0.8f);
collider.offset = new Vector2(0f, -0.05f);
}
private void EnsureVisual()
{
SpriteRenderer renderer = GetComponent<SpriteRenderer>();
if (renderer == null)
{
renderer = gameObject.AddComponent<SpriteRenderer>();
}
renderer.sprite = ProceduralWorldArt.CreatePlayerSprite();
renderer.sortingOrder = 10;
}
}
}
@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: c9187e81c6ec8da4599e04a1694ec18b
-8
View File
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: c58482c592be1ac41ad4ad194e9175c0
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
-8
View File
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: a6692637cf5eb1b41a9d619e7557b2f0
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,16 +0,0 @@
{
"name": "VoxelWorld.Runtime",
"rootNamespace": "InfiniteWorld.VoxelWorld",
"references": [
"UniTask"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}
@@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: f0a085d6765d01448bb424934e24ed9c
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,375 +0,0 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace InfiniteWorld.VoxelWorld
{
public enum VoxelSurfaceType : byte
{
CliffTop,
CliffSide,
Dirt,
WalkableSurface
}
[CreateAssetMenu(menuName = "Infinite World/Voxel Biome Profile", fileName = "VoxelBiomeProfile")]
public sealed class VoxelBiomeProfile : ScriptableObject
{
public Sprite cliffTopSprite;
public Sprite cliffSideSprite;
public Sprite dirtSprite;
public Sprite walkableSurfaceSprite;
public Sprite GetSprite(VoxelSurfaceType surfaceType)
{
return surfaceType switch
{
VoxelSurfaceType.CliffTop => cliffTopSprite,
VoxelSurfaceType.CliffSide => cliffSideSprite,
VoxelSurfaceType.Dirt => dirtSprite,
VoxelSurfaceType.WalkableSurface => walkableSurfaceSprite,
_ => walkableSurfaceSprite
};
}
public bool HasAnyAssignedSprites()
{
return cliffTopSprite != null || cliffSideSprite != null || dirtSprite != null || walkableSurfaceSprite != null;
}
}
internal sealed class VoxelWorldAtlas : IDisposable
{
private const int FallbackTileSize = 32;
private const string ShaderName = "Infinite World/VoxelWorld/TextureArrayUnlit";
private static readonly VoxelSurfaceType[] SurfaceOrder =
{
VoxelSurfaceType.CliffTop,
VoxelSurfaceType.CliffSide,
VoxelSurfaceType.Dirt,
VoxelSurfaceType.WalkableSurface
};
private readonly Dictionary<int, Dictionary<VoxelSurfaceType, int>> layerLookup;
private VoxelWorldAtlas(Texture2DArray textureArray, Material material, Dictionary<int, Dictionary<VoxelSurfaceType, int>> layers)
{
TextureArray = textureArray;
Material = material;
layerLookup = layers;
}
public Texture2DArray TextureArray { get; }
public Material Material { get; }
public int BiomeCount => layerLookup.Count;
public int GetTextureLayer(int biomeIndex, VoxelSurfaceType surfaceType)
{
if (layerLookup.TryGetValue(biomeIndex, out Dictionary<VoxelSurfaceType, int> biomeLayers) && biomeLayers.TryGetValue(surfaceType, out int layer))
{
return layer;
}
return layerLookup[0][surfaceType];
}
public static VoxelWorldAtlas CreateRuntimeAtlas(IReadOnlyList<VoxelBiomeProfile> biomeProfiles)
{
List<VoxelBiomeProfile> sourceBiomes = new List<VoxelBiomeProfile>();
if (biomeProfiles != null)
{
for (int i = 0; i < biomeProfiles.Count; i++)
{
if (biomeProfiles[i] != null)
{
sourceBiomes.Add(biomeProfiles[i]);
}
}
}
if (sourceBiomes.Count == 0)
{
sourceBiomes.Add(null);
}
int tileSize = DetermineTileSize(sourceBiomes);
int layerCount = sourceBiomes.Count * SurfaceOrder.Length;
Texture2DArray textureArray = new Texture2DArray(tileSize, tileSize, layerCount, TextureFormat.RGBA32, false, false)
{
filterMode = FilterMode.Point,
wrapMode = TextureWrapMode.Repeat,
anisoLevel = 1,
name = "VoxelWorld_RuntimeTextureArray",
hideFlags = HideFlags.HideAndDontSave
};
Dictionary<int, Dictionary<VoxelSurfaceType, int>> layers = new Dictionary<int, Dictionary<VoxelSurfaceType, int>>(sourceBiomes.Count);
int layerIndex = 0;
for (int biomeIndex = 0; biomeIndex < sourceBiomes.Count; biomeIndex++)
{
Dictionary<VoxelSurfaceType, int> biomeLayers = new Dictionary<VoxelSurfaceType, int>(SurfaceOrder.Length);
layers[biomeIndex] = biomeLayers;
for (int surfaceIndex = 0; surfaceIndex < SurfaceOrder.Length; surfaceIndex++)
{
VoxelSurfaceType surfaceType = SurfaceOrder[surfaceIndex];
Texture2D tileTexture = BuildSurfaceTexture(sourceBiomes[biomeIndex], surfaceType, tileSize);
CopyTextureToArrayLayer(tileTexture, textureArray, layerIndex);
biomeLayers[surfaceType] = layerIndex;
layerIndex++;
if (Application.isPlaying)
{
UnityEngine.Object.Destroy(tileTexture);
}
else
{
UnityEngine.Object.DestroyImmediate(tileTexture);
}
}
}
textureArray.Apply(false, false);
Shader shader = Shader.Find(ShaderName);
if (shader == null)
{
shader = Shader.Find("Universal Render Pipeline/Unlit") ?? Shader.Find("Standard");
Debug.LogError($"Shader '{ShaderName}' was not found. Falling back to '{shader?.name ?? "<missing>"}', but voxel texture array sampling will not work until the shader asset is imported correctly.");
}
Material material = new Material(shader)
{
name = "VoxelWorld_RuntimeMaterial",
hideFlags = HideFlags.HideAndDontSave
};
if (material.HasProperty("_TextureArray"))
{
material.SetTexture("_TextureArray", textureArray);
}
if (material.HasProperty("_BaseColor"))
{
material.SetColor("_BaseColor", Color.white);
}
if (material.HasProperty("_Color"))
{
material.SetColor("_Color", Color.white);
}
return new VoxelWorldAtlas(textureArray, material, layers);
}
public void Dispose()
{
if (Application.isPlaying)
{
UnityEngine.Object.Destroy(TextureArray);
UnityEngine.Object.Destroy(Material);
}
else
{
UnityEngine.Object.DestroyImmediate(TextureArray);
UnityEngine.Object.DestroyImmediate(Material);
}
}
private static int DetermineTileSize(IReadOnlyList<VoxelBiomeProfile> biomeProfiles)
{
int tileSize = FallbackTileSize;
for (int biomeIndex = 0; biomeIndex < biomeProfiles.Count; biomeIndex++)
{
VoxelBiomeProfile biome = biomeProfiles[biomeIndex];
for (int surfaceIndex = 0; surfaceIndex < SurfaceOrder.Length; surfaceIndex++)
{
Sprite sprite = biome != null ? biome.GetSprite(SurfaceOrder[surfaceIndex]) : null;
if (sprite == null)
{
continue;
}
Rect rect = sprite.textureRect;
tileSize = Mathf.Max(tileSize, Mathf.CeilToInt(Mathf.Max(rect.width, rect.height)));
}
}
return Mathf.Max(FallbackTileSize, tileSize);
}
private static Texture2D BuildSurfaceTexture(VoxelBiomeProfile biome, VoxelSurfaceType surfaceType, int tileSize)
{
Texture2D texture = new Texture2D(tileSize, tileSize, TextureFormat.RGBA32, false)
{
filterMode = FilterMode.Point,
wrapMode = TextureWrapMode.Repeat,
hideFlags = HideFlags.HideAndDontSave
};
Fill(texture, new Color(1f, 0f, 1f, 1f));
Sprite sprite = biome != null ? biome.GetSprite(surfaceType) : null;
if (sprite == null || !TryBlitSprite(texture, sprite))
{
DrawFallbackSurface(texture, new RectInt(0, 0, tileSize, tileSize), surfaceType);
}
texture.Apply(false, false);
return texture;
}
private static void CopyTextureToArrayLayer(Texture2D source, Texture2DArray destination, int layerIndex)
{
bool canUseCopyTexture = SystemInfo.supports2DArrayTextures &&
SystemInfo.copyTextureSupport != UnityEngine.Rendering.CopyTextureSupport.None &&
source.width == destination.width &&
source.height == destination.height;
if (canUseCopyTexture)
{
try
{
Graphics.CopyTexture(source, 0, 0, destination, layerIndex, 0);
return;
}
catch (Exception)
{
// Fall back to CPU copy when runtime copy is unavailable for this platform/import setup.
}
}
destination.SetPixels(source.GetPixels(), layerIndex, 0);
}
private static bool TryBlitSprite(Texture2D target, Sprite sprite)
{
try
{
Texture2D source = sprite.texture;
Rect spriteRect = sprite.textureRect;
for (int y = 0; y < target.height; y++)
{
float sampleY = Mathf.Lerp(spriteRect.yMin, spriteRect.yMax - 1f, y / (float)Mathf.Max(1, target.height - 1));
int sourceY = Mathf.Clamp(Mathf.RoundToInt(sampleY), 0, source.height - 1);
for (int x = 0; x < target.width; x++)
{
float sampleX = Mathf.Lerp(spriteRect.xMin, spriteRect.xMax - 1f, x / (float)Mathf.Max(1, target.width - 1));
int sourceX = Mathf.Clamp(Mathf.RoundToInt(sampleX), 0, source.width - 1);
target.SetPixel(x, y, source.GetPixel(sourceX, sourceY));
}
}
return true;
}
catch (UnityException)
{
Debug.LogWarning($"Sprite '{sprite.name}' texture is not readable. Falling back to generated tile for voxel biome texture array.");
return false;
}
}
private static void DrawFallbackSurface(Texture2D texture, RectInt rect, VoxelSurfaceType surfaceType)
{
Color baseColor;
Color accentColor;
switch (surfaceType)
{
case VoxelSurfaceType.CliffTop:
baseColor = new Color(0.40f, 0.72f, 0.28f, 1f);
accentColor = new Color(0.18f, 0.35f, 0.13f, 1f);
break;
case VoxelSurfaceType.CliffSide:
baseColor = new Color(0.47f, 0.34f, 0.22f, 1f);
accentColor = new Color(0.25f, 0.52f, 0.18f, 1f);
break;
case VoxelSurfaceType.Dirt:
baseColor = new Color(0.44f, 0.31f, 0.20f, 1f);
accentColor = new Color(0.31f, 0.20f, 0.12f, 1f);
break;
default:
baseColor = new Color(0.28f, 0.58f, 0.25f, 1f);
accentColor = new Color(0.18f, 0.40f, 0.16f, 1f);
break;
}
FillRect(texture, rect, baseColor);
ApplyNoise(texture, rect, 0.06f, 101 + (int)surfaceType * 43);
if (surfaceType == VoxelSurfaceType.CliffSide)
{
FillRect(texture, new RectInt(rect.x, rect.yMax - Mathf.Max(4, rect.height / 6), rect.width, Mathf.Max(4, rect.height / 6)), accentColor);
}
else if (surfaceType == VoxelSurfaceType.CliffTop)
{
FillRect(texture, new RectInt(rect.x, rect.yMax - Mathf.Max(4, rect.height / 5), rect.width, Mathf.Max(4, rect.height / 5)), accentColor);
}
else
{
for (int y = rect.y + 4; y < rect.yMax - 4; y += Mathf.Max(6, rect.height / 4))
{
for (int x = rect.x + 4; x < rect.xMax - 4; x += Mathf.Max(6, rect.width / 4))
{
DrawDisc(texture, x, y, 2, accentColor);
}
}
}
}
private static void Fill(Texture2D texture, Color color)
{
Color[] pixels = new Color[texture.width * texture.height];
for (int i = 0; i < pixels.Length; i++)
{
pixels[i] = color;
}
texture.SetPixels(pixels);
}
private static void FillRect(Texture2D texture, RectInt rect, Color color)
{
for (int y = rect.yMin; y < rect.yMax; y++)
{
for (int x = rect.xMin; x < rect.xMax; x++)
{
texture.SetPixel(x, y, color);
}
}
}
private static void DrawDisc(Texture2D texture, int centerX, int centerY, int radius, Color color)
{
int sqrRadius = radius * radius;
for (int y = -radius; y <= radius; y++)
{
for (int x = -radius; x <= radius; x++)
{
if (x * x + y * y > sqrRadius)
{
continue;
}
texture.SetPixel(centerX + x, centerY + y, color);
}
}
}
private static void ApplyNoise(Texture2D texture, RectInt rect, float strength, int seed)
{
for (int y = rect.yMin; y < rect.yMax; y++)
{
for (int x = rect.xMin; x < rect.xMax; x++)
{
Color pixel = texture.GetPixel(x, y);
float noise = Mathf.PerlinNoise((x + seed) * 0.18f, (y - seed) * 0.18f) - 0.5f;
texture.SetPixel(x, y, pixel * (1f + noise * strength * 2f));
}
}
}
}
}
@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 981858f28439459429fa291e1f6cb935
@@ -1,182 +0,0 @@
using System.Collections.Generic;
using UnityEngine;
namespace InfiniteWorld.VoxelWorld
{
[CreateAssetMenu(menuName = "Infinite World/Voxel World Config", fileName = "VoxelWorldConfig")]
public sealed class VoxelWorldConfig : ScriptableObject
{
[Header("Streaming")]
[Min(8)] public int chunkSize = 16;
[Min(1)] public int generationRadius = 3;
[Min(0)] public int blockingGenerationRadius;
public int seed = 12345;
[Min(1)] public int maxMountainHeight = 3;
[Header("Shape Noise")]
public float macroNoiseScale = 0.05f;
public float detailNoiseScale = 0.12f;
public float ridgeNoiseScale = 0.18f;
public float wallThreshold = 0.6f;
public float rockBias = 0.04f;
[Min(0)] public int smoothingPasses = 2;
[Header("Global Passes")]
public float passNoiseScale = 0.018f;
public float passDetailScale = 0.041f;
public float passThreshold = 0.22f;
public float passFeather = 0.12f;
[Header("Height")]
public float heightNoiseScale = 0.08f;
public float terraceNoiseScale = 0.17f;
public float heightBias = 0.05f;
[Header("Biomes")]
public List<VoxelBiomeProfile> biomeProfiles = new List<VoxelBiomeProfile>();
public float biomeNoiseScale = 0.02f;
[Min(1f)] public float biomeSize = 48f;
[Header("Runtime")]
[Min(1)] public int maxAsyncChunkJobs = 2;
[Min(1)] public int maxChunkBuildsPerFrame = 1;
[Min(1)] public int maxChunkMeshBuildsPerFrame = 1;
[Min(1)] public int maxColliderAppliesPerFrame = 1;
[Min(0)] public int maxNeighborRefreshesPerFrame = 2;
[Min(1)] public int renderRegionSizeInChunks = 4;
[Min(1)] public int maxRegionBuildsPerFrame = 1;
}
internal readonly struct VoxelWorldResolvedSettings
{
private static readonly IReadOnlyList<VoxelBiomeProfile> EmptyBiomes = System.Array.Empty<VoxelBiomeProfile>();
public static readonly VoxelWorldResolvedSettings Default = Resolve(null);
public VoxelWorldResolvedSettings(
int chunkSize,
int generationRadius,
int blockingGenerationRadius,
int seed,
int maxMountainHeight,
float macroNoiseScale,
float detailNoiseScale,
float ridgeNoiseScale,
float wallThreshold,
float rockBias,
int smoothingPasses,
float passNoiseScale,
float passDetailScale,
float passThreshold,
float passFeather,
float heightNoiseScale,
float terraceNoiseScale,
float heightBias,
IReadOnlyList<VoxelBiomeProfile> biomeProfiles,
float biomeNoiseScale,
float biomeSize,
int maxAsyncChunkJobs,
int maxChunkBuildsPerFrame,
int maxChunkMeshBuildsPerFrame,
int maxColliderAppliesPerFrame,
int maxNeighborRefreshesPerFrame,
int renderRegionSizeInChunks,
int maxRegionBuildsPerFrame)
{
ChunkSize = chunkSize;
GenerationRadius = generationRadius;
BlockingGenerationRadius = blockingGenerationRadius;
Seed = seed;
MaxMountainHeight = maxMountainHeight;
MacroNoiseScale = macroNoiseScale;
DetailNoiseScale = detailNoiseScale;
RidgeNoiseScale = ridgeNoiseScale;
WallThreshold = wallThreshold;
RockBias = rockBias;
SmoothingPasses = smoothingPasses;
PassNoiseScale = passNoiseScale;
PassDetailScale = passDetailScale;
PassThreshold = passThreshold;
PassFeather = passFeather;
HeightNoiseScale = heightNoiseScale;
TerraceNoiseScale = terraceNoiseScale;
HeightBias = heightBias;
BiomeProfiles = biomeProfiles;
BiomeNoiseScale = biomeNoiseScale;
BiomeSize = biomeSize;
MaxAsyncChunkJobs = maxAsyncChunkJobs;
MaxChunkBuildsPerFrame = maxChunkBuildsPerFrame;
MaxChunkMeshBuildsPerFrame = maxChunkMeshBuildsPerFrame;
MaxColliderAppliesPerFrame = maxColliderAppliesPerFrame;
MaxNeighborRefreshesPerFrame = maxNeighborRefreshesPerFrame;
RenderRegionSizeInChunks = renderRegionSizeInChunks;
MaxRegionBuildsPerFrame = maxRegionBuildsPerFrame;
}
public int ChunkSize { get; }
public int GenerationRadius { get; }
public int BlockingGenerationRadius { get; }
public int Seed { get; }
public int MaxMountainHeight { get; }
public float MacroNoiseScale { get; }
public float DetailNoiseScale { get; }
public float RidgeNoiseScale { get; }
public float WallThreshold { get; }
public float RockBias { get; }
public int SmoothingPasses { get; }
public float PassNoiseScale { get; }
public float PassDetailScale { get; }
public float PassThreshold { get; }
public float PassFeather { get; }
public float HeightNoiseScale { get; }
public float TerraceNoiseScale { get; }
public float HeightBias { get; }
public IReadOnlyList<VoxelBiomeProfile> BiomeProfiles { get; }
public float BiomeNoiseScale { get; }
public float BiomeSize { get; }
public int MaxAsyncChunkJobs { get; }
public int MaxChunkBuildsPerFrame { get; }
public int MaxChunkMeshBuildsPerFrame { get; }
public int MaxColliderAppliesPerFrame { get; }
public int MaxNeighborRefreshesPerFrame { get; }
public int RenderRegionSizeInChunks { get; }
public int MaxRegionBuildsPerFrame { get; }
public static VoxelWorldResolvedSettings Resolve(VoxelWorldConfig config)
{
IReadOnlyList<VoxelBiomeProfile> biomes = config != null && config.biomeProfiles != null
? config.biomeProfiles
: EmptyBiomes;
return new VoxelWorldResolvedSettings(
Mathf.Max(8, config != null ? config.chunkSize : 16),
Mathf.Max(1, config != null ? config.generationRadius : 3),
Mathf.Max(0, config != null ? config.blockingGenerationRadius : 0),
config != null ? config.seed : 12345,
Mathf.Max(1, config != null ? config.maxMountainHeight : 3),
config != null ? config.macroNoiseScale : 0.05f,
config != null ? config.detailNoiseScale : 0.12f,
config != null ? config.ridgeNoiseScale : 0.18f,
config != null ? config.wallThreshold : 0.6f,
config != null ? config.rockBias : 0.04f,
Mathf.Max(0, config != null ? config.smoothingPasses : 2),
config != null ? config.passNoiseScale : 0.018f,
config != null ? config.passDetailScale : 0.041f,
config != null ? config.passThreshold : 0.22f,
config != null ? config.passFeather : 0.12f,
config != null ? config.heightNoiseScale : 0.08f,
config != null ? config.terraceNoiseScale : 0.17f,
config != null ? config.heightBias : 0.05f,
biomes,
config != null ? config.biomeNoiseScale : 0.02f,
Mathf.Max(1f, config != null ? config.biomeSize : 48f),
Mathf.Max(1, config != null ? config.maxAsyncChunkJobs : 2),
Mathf.Max(1, config != null ? config.maxChunkBuildsPerFrame : 1),
Mathf.Max(1, config != null ? config.maxChunkMeshBuildsPerFrame : 1),
Mathf.Max(1, config != null ? config.maxColliderAppliesPerFrame : 1),
Mathf.Max(0, config != null ? config.maxNeighborRefreshesPerFrame : 2),
Mathf.Max(1, config != null ? config.renderRegionSizeInChunks : 4),
Mathf.Max(1, config != null ? config.maxRegionBuildsPerFrame : 1));
}
}
}
@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 0cac3da4969c2f94985de9f5fb30a682
@@ -1,165 +0,0 @@
using UnityEngine;
namespace InfiniteWorld.VoxelWorld
{
public sealed partial class VoxelWorldGenerator
{
private ChunkBuildResult GenerateChunkData(Vector2Int coord, int version, int session, int runtimeId)
{
int margin = Mathf.Max(2, smoothingPasses + 1);
int sampleSize = chunkSize + margin * 2;
bool[,] sampled = new bool[sampleSize, sampleSize];
for (int z = 0; z < sampleSize; z++)
{
for (int x = 0; x < sampleSize; x++)
{
int localX = x - margin;
int localZ = z - margin;
Vector2Int worldCell = ChunkToWorldCell(coord, localX, localZ);
sampled[x, z] = SampleRock(worldCell);
}
}
for (int pass = 0; pass < smoothingPasses; pass++)
{
sampled = SmoothSampledMask(sampled);
}
int[] heights = new int[chunkSize * chunkSize];
byte[] biomeIndices = new byte[chunkSize * chunkSize];
for (int z = 0; z < chunkSize; z++)
{
for (int x = 0; x < chunkSize; x++)
{
Vector2Int worldCell = ChunkToWorldCell(coord, x, z);
biomeIndices[z * chunkSize + x] = SampleBiomeIndex(worldCell);
bool hasMountain = sampled[x + margin, z + margin];
if (!hasMountain)
{
continue;
}
heights[z * chunkSize + x] = SampleHeight(worldCell);
}
}
return new ChunkBuildResult(coord, heights, biomeIndices, version, session, runtimeId);
}
private bool[,] SmoothSampledMask(bool[,] source)
{
int width = source.GetLength(0);
int height = source.GetLength(1);
bool[,] result = new bool[width, height];
for (int z = 0; z < height; z++)
{
for (int x = 0; x < width; x++)
{
int solidNeighbors = CountSampledNeighbors(source, x, z);
if (solidNeighbors >= 5)
{
result[x, z] = true;
}
else if (solidNeighbors <= 2)
{
result[x, z] = false;
}
else
{
result[x, z] = source[x, z];
}
}
}
return result;
}
private static int CountSampledNeighbors(bool[,] sampled, int x, int z)
{
int width = sampled.GetLength(0);
int height = sampled.GetLength(1);
int count = 0;
for (int oz = -1; oz <= 1; oz++)
{
for (int ox = -1; ox <= 1; ox++)
{
if (ox == 0 && oz == 0)
{
continue;
}
int nx = x + ox;
int nz = z + oz;
if (nx < 0 || nz < 0 || nx >= width || nz >= height)
{
count++;
continue;
}
if (sampled[nx, nz])
{
count++;
}
}
}
return count;
}
private bool SampleRock(Vector2Int worldCell)
{
float macro = Mathf.PerlinNoise((worldCell.x + seed * 0.13f) * macroNoiseScale, (worldCell.y - seed * 0.17f) * macroNoiseScale);
float detail = Mathf.PerlinNoise((worldCell.x - seed * 0.23f) * detailNoiseScale, (worldCell.y + seed * 0.19f) * detailNoiseScale);
float ridge = 1f - Mathf.Abs(Mathf.PerlinNoise((worldCell.x + seed * 0.31f) * ridgeNoiseScale, (worldCell.y + seed * 0.29f) * ridgeNoiseScale) * 2f - 1f);
float rockValue = macro * 0.62f + detail * 0.18f + ridge * 0.20f + rockBias;
if (IsInsideGlobalPass(worldCell))
{
rockValue -= 0.45f;
}
return rockValue >= wallThreshold;
}
private bool IsInsideGlobalPass(Vector2Int worldCell)
{
float primary = Mathf.PerlinNoise((worldCell.x + seed * 0.41f) * passNoiseScale, (worldCell.y - seed * 0.43f) * passNoiseScale);
float detail = Mathf.PerlinNoise((worldCell.x - seed * 0.17f) * passDetailScale, (worldCell.y + seed * 0.23f) * passDetailScale);
float ridged = Mathf.Abs(primary * 2f - 1f);
float warped = Mathf.Lerp(ridged, Mathf.Abs(detail * 2f - 1f), 0.35f);
return warped <= passThreshold + passFeather * detail;
}
private int SampleHeight(Vector2Int worldCell)
{
float macro = Mathf.PerlinNoise((worldCell.x - seed * 0.47f) * heightNoiseScale, (worldCell.y + seed * 0.37f) * heightNoiseScale);
float terrace = Mathf.PerlinNoise((worldCell.x + seed * 0.67f) * terraceNoiseScale, (worldCell.y - seed * 0.59f) * terraceNoiseScale);
float ridge = 1f - Mathf.Abs(Mathf.PerlinNoise((worldCell.x + seed * 0.71f) * ridgeNoiseScale, (worldCell.y - seed * 0.73f) * ridgeNoiseScale) * 2f - 1f);
float heightValue = macro * 0.55f + terrace * 0.2f + ridge * 0.25f + heightBias;
int height = 1 + Mathf.FloorToInt(Mathf.Clamp01(heightValue) * maxMountainHeight);
return Mathf.Clamp(height, 1, maxMountainHeight);
}
private byte SampleBiomeIndex(Vector2Int worldCell)
{
int biomeCount = Mathf.Max(1, atlasBiomeCount > 0 ? atlasBiomeCount : CountConfiguredBiomes());
if (biomeCount <= 1)
{
return 0;
}
float effectiveScale = biomeNoiseScale / Mathf.Max(1f, biomeSize);
float noise = Mathf.PerlinNoise((worldCell.x + seed * 0.83f) * effectiveScale, (worldCell.y - seed * 0.79f) * effectiveScale);
int biomeIndex = Mathf.FloorToInt(Mathf.Clamp01(noise) * biomeCount);
return (byte)Mathf.Clamp(biomeIndex, 0, biomeCount - 1);
}
private Vector2Int ChunkToWorldCell(Vector2Int coord, int localX, int localZ)
{
return new Vector2Int(coord.x * chunkSize + localX, coord.y * chunkSize + localZ);
}
}
}
@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 9da608950686fb345b172db0a56bced5
@@ -1,451 +0,0 @@
using UnityEngine;
namespace InfiniteWorld.VoxelWorld
{
public sealed partial class VoxelWorldGenerator
{
private void RenderChunk(Vector2Int coord)
{
if (!chunks.TryGetValue(coord, out ChunkRuntime runtime) || !runtime.HasData || atlas == null)
{
return;
}
runtime.EnsureCreated(coord, chunkRoot, chunkSize);
ChunkMeshBuild meshBuild = BuildChunkMesh(coord, runtime.Heights, runtime.BiomeIndices);
runtime.ApplyRenderData(meshBuild.RenderSnapshot);
EnqueueColliderApply(coord, runtime.Version, runtime.RuntimeId, meshBuild.ColliderMesh);
runtime.State = ChunkState.Rendered;
MarkRegionDirty(coord);
}
private ChunkMeshBuild BuildChunkMesh(Vector2Int coord, int[] heights, byte[] biomeIndices)
{
MeshBuffers renderBuffers = new MeshBuffers();
MeshBuffers colliderBuffers = new MeshBuffers();
BuildGroundSurface(heights, biomeIndices, renderBuffers);
BuildMountainTops(coord, heights, biomeIndices, renderBuffers, colliderBuffers);
BuildMountainSides(coord, heights, biomeIndices, renderBuffers, colliderBuffers);
ChunkRenderSnapshot renderSnapshot = renderBuffers.ToSnapshot();
return new ChunkMeshBuild(colliderBuffers.ToMesh($"Collider_{coord.x}_{coord.y}"), renderSnapshot);
}
private void BuildGroundSurface(int[] heights, byte[] biomeIndices, MeshBuffers renderBuffers)
{
bool[,] visited = new bool[chunkSize, chunkSize];
for (int z = 0; z < chunkSize; z++)
{
for (int x = 0; x < chunkSize; x++)
{
int index = z * chunkSize + x;
if (visited[x, z] || heights[index] > 0)
{
continue;
}
byte biomeIndex = biomeIndices[index];
int width = 1;
while (x + width < chunkSize && !visited[x + width, z] && heights[z * chunkSize + x + width] == 0 && biomeIndices[z * chunkSize + x + width] == biomeIndex)
{
width++;
}
int depth = 1;
bool canGrow = true;
while (z + depth < chunkSize && canGrow)
{
for (int ix = 0; ix < width; ix++)
{
int candidateIndex = (z + depth) * chunkSize + x + ix;
if (visited[x + ix, z + depth] || heights[candidateIndex] > 0 || biomeIndices[candidateIndex] != biomeIndex)
{
canGrow = false;
break;
}
}
if (canGrow)
{
depth++;
}
}
for (int dz = 0; dz < depth; dz++)
{
for (int dx = 0; dx < width; dx++)
{
visited[x + dx, z + dz] = true;
}
}
AddTopQuad(renderBuffers, x, z, width, depth, 0f, atlas.GetTextureLayer(biomeIndex, VoxelSurfaceType.WalkableSurface));
}
}
}
private void BuildMountainTops(Vector2Int coord, int[] heights, byte[] biomeIndices, MeshBuffers renderBuffers, MeshBuffers colliderBuffers)
{
bool[,] visited = new bool[chunkSize, chunkSize];
for (int z = 0; z < chunkSize; z++)
{
for (int x = 0; x < chunkSize; x++)
{
int height = heights[z * chunkSize + x];
if (height <= 0 || visited[x, z])
{
continue;
}
int index = z * chunkSize + x;
VoxelSurfaceType surfaceType = IsCliffTop(ChunkToWorldCell(coord, x, z), height) ? VoxelSurfaceType.CliffTop : VoxelSurfaceType.WalkableSurface;
byte biomeIndex = biomeIndices[index];
int width = 1;
while (x + width < chunkSize && !visited[x + width, z])
{
int candidateIndex = z * chunkSize + x + width;
if (heights[candidateIndex] != height || biomeIndices[candidateIndex] != biomeIndex)
{
break;
}
VoxelSurfaceType candidateSurface = IsCliffTop(ChunkToWorldCell(coord, x + width, z), height) ? VoxelSurfaceType.CliffTop : VoxelSurfaceType.WalkableSurface;
if (candidateSurface != surfaceType)
{
break;
}
width++;
}
int depth = 1;
bool canGrow = true;
while (z + depth < chunkSize && canGrow)
{
for (int ix = 0; ix < width; ix++)
{
if (visited[x + ix, z + depth])
{
canGrow = false;
break;
}
int candidateIndex = (z + depth) * chunkSize + x + ix;
if (heights[candidateIndex] != height || biomeIndices[candidateIndex] != biomeIndex)
{
canGrow = false;
break;
}
VoxelSurfaceType candidateSurface = IsCliffTop(ChunkToWorldCell(coord, x + ix, z + depth), height) ? VoxelSurfaceType.CliffTop : VoxelSurfaceType.WalkableSurface;
if (candidateSurface != surfaceType)
{
canGrow = false;
break;
}
}
if (canGrow)
{
depth++;
}
}
for (int dz = 0; dz < depth; dz++)
{
for (int dx = 0; dx < width; dx++)
{
visited[x + dx, z + dz] = true;
}
}
int textureLayer = atlas.GetTextureLayer(biomeIndex, surfaceType);
AddTopQuad(renderBuffers, x, z, width, depth, height, textureLayer);
AddTopQuad(colliderBuffers, x, z, width, depth, height, 0, false);
}
}
}
private void BuildMountainSides(Vector2Int coord, int[] heights, byte[] biomeIndices, MeshBuffers renderBuffers, MeshBuffers colliderBuffers)
{
for (int z = 0; z < chunkSize; z++)
{
BuildNorthSouthFaceSlice(coord, heights, biomeIndices, renderBuffers, colliderBuffers, z, true);
BuildNorthSouthFaceSlice(coord, heights, biomeIndices, renderBuffers, colliderBuffers, z, false);
}
for (int x = 0; x < chunkSize; x++)
{
BuildEastWestFaceSlice(coord, heights, biomeIndices, renderBuffers, colliderBuffers, x, true);
BuildEastWestFaceSlice(coord, heights, biomeIndices, renderBuffers, colliderBuffers, x, false);
}
}
private void BuildNorthSouthFaceSlice(Vector2Int coord, int[] heights, byte[] biomeIndices, MeshBuffers renderBuffers, MeshBuffers colliderBuffers, int z, bool north)
{
bool[,] visited = new bool[chunkSize, maxMountainHeight];
for (int x = 0; x < chunkSize; x++)
{
for (int y = 0; y < maxMountainHeight; y++)
{
if (visited[x, y] || !TryGetNorthSouthFace(coord, heights, biomeIndices, x, z, y, north, out byte biomeIndex, out VoxelSurfaceType surfaceType, out int textureLayer))
{
continue;
}
int width = 1;
while (x + width < chunkSize && !visited[x + width, y] && TryGetNorthSouthFace(coord, heights, biomeIndices, x + width, z, y, north, out byte candidateBiome, out VoxelSurfaceType candidateSurface, out int candidateTexture) && candidateBiome == biomeIndex && candidateSurface == surfaceType && candidateTexture == textureLayer)
{
width++;
}
int height = 1;
bool canGrow = true;
while (y + height < maxMountainHeight && canGrow)
{
for (int dx = 0; dx < width; dx++)
{
if (visited[x + dx, y + height] || !TryGetNorthSouthFace(coord, heights, biomeIndices, x + dx, z, y + height, north, out byte candidateBiome, out VoxelSurfaceType candidateSurface, out int candidateTexture) || candidateBiome != biomeIndex || candidateSurface != surfaceType || candidateTexture != textureLayer)
{
canGrow = false;
break;
}
}
if (canGrow)
{
height++;
}
}
for (int dy = 0; dy < height; dy++)
{
for (int dx = 0; dx < width; dx++)
{
visited[x + dx, y + dy] = true;
}
}
float faceZ = north ? z + 1f : z;
Vector3 bl = new Vector3(x, y, faceZ);
Vector3 br = new Vector3(x + width, y, faceZ);
Vector3 tr = new Vector3(x + width, y + height, faceZ);
Vector3 tl = new Vector3(x, y + height, faceZ);
if (north)
{
AddVerticalQuad(renderBuffers, bl, br, tr, tl, width, height, textureLayer, Vector3.forward);
AddVerticalQuad(colliderBuffers, bl, br, tr, tl, width, height, 0, Vector3.forward, false);
}
else
{
AddVerticalQuad(renderBuffers, br, bl, tl, tr, width, height, textureLayer, Vector3.back);
AddVerticalQuad(colliderBuffers, br, bl, tl, tr, width, height, 0, Vector3.back, false);
}
}
}
}
private void BuildEastWestFaceSlice(Vector2Int coord, int[] heights, byte[] biomeIndices, MeshBuffers renderBuffers, MeshBuffers colliderBuffers, int x, bool east)
{
bool[,] visited = new bool[chunkSize, maxMountainHeight];
for (int z = 0; z < chunkSize; z++)
{
for (int y = 0; y < maxMountainHeight; y++)
{
if (visited[z, y] || !TryGetEastWestFace(coord, heights, biomeIndices, x, z, y, east, out byte biomeIndex, out VoxelSurfaceType surfaceType, out int textureLayer))
{
continue;
}
int width = 1;
while (z + width < chunkSize && !visited[z + width, y] && TryGetEastWestFace(coord, heights, biomeIndices, x, z + width, y, east, out byte candidateBiome, out VoxelSurfaceType candidateSurface, out int candidateTexture) && candidateBiome == biomeIndex && candidateSurface == surfaceType && candidateTexture == textureLayer)
{
width++;
}
int height = 1;
bool canGrow = true;
while (y + height < maxMountainHeight && canGrow)
{
for (int dz = 0; dz < width; dz++)
{
if (visited[z + dz, y + height] || !TryGetEastWestFace(coord, heights, biomeIndices, x, z + dz, y + height, east, out byte candidateBiome, out VoxelSurfaceType candidateSurface, out int candidateTexture) || candidateBiome != biomeIndex || candidateSurface != surfaceType || candidateTexture != textureLayer)
{
canGrow = false;
break;
}
}
if (canGrow)
{
height++;
}
}
for (int dy = 0; dy < height; dy++)
{
for (int dz = 0; dz < width; dz++)
{
visited[z + dz, y + dy] = true;
}
}
float faceX = east ? x + 1f : x;
Vector3 bl = new Vector3(faceX, y, z);
Vector3 br = new Vector3(faceX, y, z + width);
Vector3 tr = new Vector3(faceX, y + height, z + width);
Vector3 tl = new Vector3(faceX, y + height, z);
if (east)
{
AddVerticalQuad(renderBuffers, br, bl, tl, tr, width, height, textureLayer, Vector3.right);
AddVerticalQuad(colliderBuffers, br, bl, tl, tr, width, height, 0, Vector3.right, false);
}
else
{
AddVerticalQuad(renderBuffers, bl, br, tr, tl, width, height, textureLayer, Vector3.left);
AddVerticalQuad(colliderBuffers, bl, br, tr, tl, width, height, 0, Vector3.left, false);
}
}
}
}
private bool TryGetNorthSouthFace(Vector2Int coord, int[] heights, byte[] biomeIndices, int x, int z, int y, bool north, out byte biomeIndex, out VoxelSurfaceType surfaceType, out int textureLayer)
{
int current = heights[z * chunkSize + x];
int worldX = coord.x * chunkSize + x;
int worldZ = coord.y * chunkSize + z;
int neighbor = GetHeightAtWorldCell(new Vector2Int(worldX, north ? worldZ + 1 : worldZ - 1));
if (y >= current || y < neighbor)
{
biomeIndex = 0;
surfaceType = VoxelSurfaceType.Dirt;
textureLayer = 0;
return false;
}
biomeIndex = biomeIndices[z * chunkSize + x];
surfaceType = y == current - 1 ? VoxelSurfaceType.CliffSide : VoxelSurfaceType.Dirt;
textureLayer = atlas.GetTextureLayer(biomeIndex, surfaceType);
return true;
}
private bool TryGetEastWestFace(Vector2Int coord, int[] heights, byte[] biomeIndices, int x, int z, int y, bool east, out byte biomeIndex, out VoxelSurfaceType surfaceType, out int textureLayer)
{
int current = heights[z * chunkSize + x];
int worldX = coord.x * chunkSize + x;
int worldZ = coord.y * chunkSize + z;
int neighbor = GetHeightAtWorldCell(new Vector2Int(east ? worldX + 1 : worldX - 1, worldZ));
if (y >= current || y < neighbor)
{
biomeIndex = 0;
surfaceType = VoxelSurfaceType.Dirt;
textureLayer = 0;
return false;
}
biomeIndex = biomeIndices[z * chunkSize + x];
surfaceType = y == current - 1 ? VoxelSurfaceType.CliffSide : VoxelSurfaceType.Dirt;
textureLayer = atlas.GetTextureLayer(biomeIndex, surfaceType);
return true;
}
private void AddTopQuad(MeshBuffers buffers, float x, float z, float width, float depth, float height, int textureLayer, bool withUv = true)
{
int baseIndex = buffers.Vertices.Count;
buffers.Vertices.Add(new Vector3(x, height, z));
buffers.Vertices.Add(new Vector3(x + width, height, z));
buffers.Vertices.Add(new Vector3(x + width, height, z + depth));
buffers.Vertices.Add(new Vector3(x, height, z + depth));
buffers.Normals.Add(Vector3.up);
buffers.Normals.Add(Vector3.up);
buffers.Normals.Add(Vector3.up);
buffers.Normals.Add(Vector3.up);
if (withUv)
{
buffers.Uvs.Add(new Vector2(0f, 0f));
buffers.Uvs.Add(new Vector2(width, 0f));
buffers.Uvs.Add(new Vector2(width, depth));
buffers.Uvs.Add(new Vector2(0f, depth));
Vector2 textureData = new Vector2(textureLayer, 0f);
buffers.TextureData.Add(textureData);
buffers.TextureData.Add(textureData);
buffers.TextureData.Add(textureData);
buffers.TextureData.Add(textureData);
}
buffers.Triangles.Add(baseIndex);
buffers.Triangles.Add(baseIndex + 2);
buffers.Triangles.Add(baseIndex + 1);
buffers.Triangles.Add(baseIndex);
buffers.Triangles.Add(baseIndex + 3);
buffers.Triangles.Add(baseIndex + 2);
}
private void AddVerticalQuad(MeshBuffers buffers, Vector3 bottomLeft, Vector3 bottomRight, Vector3 topRight, Vector3 topLeft, float width, float height, int textureLayer, Vector3 normal, bool withUv = true)
{
int baseIndex = buffers.Vertices.Count;
buffers.Vertices.Add(bottomLeft);
buffers.Vertices.Add(bottomRight);
buffers.Vertices.Add(topRight);
buffers.Vertices.Add(topLeft);
buffers.Normals.Add(normal);
buffers.Normals.Add(normal);
buffers.Normals.Add(normal);
buffers.Normals.Add(normal);
if (withUv)
{
buffers.Uvs.Add(new Vector2(0f, 0f));
buffers.Uvs.Add(new Vector2(width, 0f));
buffers.Uvs.Add(new Vector2(width, height));
buffers.Uvs.Add(new Vector2(0f, height));
Vector2 textureData = new Vector2(textureLayer, 0f);
buffers.TextureData.Add(textureData);
buffers.TextureData.Add(textureData);
buffers.TextureData.Add(textureData);
buffers.TextureData.Add(textureData);
}
buffers.Triangles.Add(baseIndex);
buffers.Triangles.Add(baseIndex + 1);
buffers.Triangles.Add(baseIndex + 2);
buffers.Triangles.Add(baseIndex);
buffers.Triangles.Add(baseIndex + 2);
buffers.Triangles.Add(baseIndex + 3);
}
private bool IsCliffTop(Vector2Int worldCell, int currentHeight)
{
return GetHeightAtWorldCell(worldCell + Vector2Int.up) < currentHeight ||
GetHeightAtWorldCell(worldCell + Vector2Int.right) < currentHeight ||
GetHeightAtWorldCell(worldCell + Vector2Int.down) < currentHeight ||
GetHeightAtWorldCell(worldCell + Vector2Int.left) < currentHeight;
}
private int GetHeightAtWorldCell(Vector2Int worldCell)
{
Vector2Int coord = new Vector2Int(
Mathf.FloorToInt(worldCell.x / (float)chunkSize),
Mathf.FloorToInt(worldCell.y / (float)chunkSize));
if (!chunks.TryGetValue(coord, out ChunkRuntime runtime) || !runtime.HasData)
{
return SampleRock(worldCell) ? SampleHeight(worldCell) : 0;
}
int localX = worldCell.x - coord.x * chunkSize;
int localZ = worldCell.y - coord.y * chunkSize;
if (localX < 0 || localZ < 0 || localX >= chunkSize || localZ >= chunkSize)
{
return SampleRock(worldCell) ? SampleHeight(worldCell) : 0;
}
return runtime.Heights[localZ * chunkSize + localX];
}
}
}
@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 811ff92b4e36193499cad8631c9443a7
@@ -1,339 +0,0 @@
using System.Collections.Generic;
using UnityEngine;
namespace InfiniteWorld.VoxelWorld
{
public sealed partial class VoxelWorldGenerator
{
private struct ChunkBuildResult
{
public ChunkBuildResult(Vector2Int coord, int[] heights, byte[] biomeIndices, int version, int session, int runtimeId)
{
Coord = coord;
Heights = heights;
BiomeIndices = biomeIndices;
Version = version;
Session = session;
RuntimeId = runtimeId;
}
public Vector2Int Coord { get; }
public int[] Heights { get; }
public byte[] BiomeIndices { get; }
public int Version { get; }
public int Session { get; }
public int RuntimeId { get; }
}
private struct ChunkMeshBuild
{
public ChunkMeshBuild(Mesh colliderMesh, ChunkRenderSnapshot renderSnapshot)
{
ColliderMesh = colliderMesh;
RenderSnapshot = renderSnapshot;
}
public Mesh ColliderMesh { get; }
public ChunkRenderSnapshot RenderSnapshot { get; }
}
private struct PendingColliderMeshApply
{
public PendingColliderMeshApply(Vector2Int coord, int version, int runtimeId, Mesh colliderMesh)
{
Coord = coord;
Version = version;
RuntimeId = runtimeId;
ColliderMesh = colliderMesh;
}
public Vector2Int Coord { get; }
public int Version { get; }
public int RuntimeId { get; }
public Mesh ColliderMesh { get; }
}
private sealed class ChunkRenderSnapshot
{
public ChunkRenderSnapshot(Vector3[] vertices, Vector3[] normals, Vector2[] uv0, Vector2[] uv1, int[] triangles)
{
Vertices = vertices;
Normals = normals;
Uv0 = uv0;
Uv1 = uv1;
Triangles = triangles;
}
public Vector3[] Vertices { get; }
public Vector3[] Normals { get; }
public Vector2[] Uv0 { get; }
public Vector2[] Uv1 { get; }
public int[] Triangles { get; }
public bool IsEmpty => Vertices == null || Vertices.Length == 0 || Triangles == null || Triangles.Length == 0;
}
private struct RegionChunkSnapshot
{
public RegionChunkSnapshot(ChunkRenderSnapshot snapshot, Vector3 localOffset)
{
Snapshot = snapshot;
LocalOffset = localOffset;
}
public ChunkRenderSnapshot Snapshot { get; }
public Vector3 LocalOffset { get; }
}
private struct RegionBuildRequest
{
public RegionBuildRequest(Vector2Int regionCoord, int version, int session, RegionChunkSnapshot[] chunks)
{
RegionCoord = regionCoord;
Version = version;
Session = session;
Chunks = chunks;
}
public Vector2Int RegionCoord { get; }
public int Version { get; }
public int Session { get; }
public RegionChunkSnapshot[] Chunks { get; }
}
private struct RegionBuildResult
{
public RegionBuildResult(Vector2Int regionCoord, int version, int session, Vector3[] vertices, Vector3[] normals, Vector2[] uv0, Vector2[] uv1, int[] triangles, Bounds bounds)
{
RegionCoord = regionCoord;
Version = version;
Session = session;
Vertices = vertices;
Normals = normals;
Uv0 = uv0;
Uv1 = uv1;
Triangles = triangles;
Bounds = bounds;
}
public Vector2Int RegionCoord { get; }
public int Version { get; }
public int Session { get; }
public Vector3[] Vertices { get; }
public Vector3[] Normals { get; }
public Vector2[] Uv0 { get; }
public Vector2[] Uv1 { get; }
public int[] Triangles { get; }
public Bounds Bounds { get; }
public bool IsEmpty => Vertices == null || Vertices.Length == 0 || Triangles == null || Triangles.Length == 0;
public static RegionBuildResult CreateEmpty(Vector2Int regionCoord, int version, int session)
{
return new RegionBuildResult(regionCoord, version, session, null, null, null, null, null, new Bounds());
}
}
private sealed class MeshBuffers
{
public readonly List<Vector3> Vertices = new List<Vector3>(512);
public readonly List<Vector3> Normals = new List<Vector3>(512);
public readonly List<Vector2> Uvs = new List<Vector2>(512);
public readonly List<Vector2> TextureData = new List<Vector2>(512);
public readonly List<int> Triangles = new List<int>(1024);
public Mesh ToMesh(string meshName)
{
Mesh mesh = new Mesh { name = meshName };
if (Vertices.Count == 0)
{
return mesh;
}
mesh.indexFormat = Vertices.Count > 65535 ? UnityEngine.Rendering.IndexFormat.UInt32 : UnityEngine.Rendering.IndexFormat.UInt16;
mesh.SetVertices(Vertices);
if (Normals.Count == Vertices.Count)
{
mesh.SetNormals(Normals);
}
if (Uvs.Count == Vertices.Count)
{
mesh.SetUVs(0, Uvs);
}
if (TextureData.Count == Vertices.Count)
{
mesh.SetUVs(1, TextureData);
}
mesh.SetTriangles(Triangles, 0, true);
mesh.RecalculateBounds();
return mesh;
}
public ChunkRenderSnapshot ToSnapshot()
{
return new ChunkRenderSnapshot(
Vertices.ToArray(),
Normals.ToArray(),
Uvs.ToArray(),
TextureData.ToArray(),
Triangles.ToArray());
}
}
private sealed class ChunkRuntime
{
public Transform Root;
public MeshCollider MountainCollider;
public BoxCollider GroundCollider;
public Mesh ColliderMesh;
public ChunkRenderSnapshot RenderSnapshot;
public int[] Heights;
public byte[] BiomeIndices;
public ChunkState State;
public int Version;
public int RuntimeId;
public bool HasData => Heights != null && BiomeIndices != null;
public bool IsRendered => State == ChunkState.Rendered && Root != null;
public void EnsureCreated(Vector2Int coord, Transform parent, int chunkSize)
{
if (Root == null)
{
GameObject chunkObject = new GameObject($"VoxelChunk_{coord.x}_{coord.y}");
chunkObject.transform.SetParent(parent, false);
Root = chunkObject.transform;
MountainCollider = chunkObject.AddComponent<MeshCollider>();
GroundCollider = chunkObject.AddComponent<BoxCollider>();
}
Root.localPosition = new Vector3(coord.x * chunkSize, 0f, coord.y * chunkSize);
GroundCollider.size = new Vector3(chunkSize, 0.2f, chunkSize);
GroundCollider.center = new Vector3(chunkSize * 0.5f, -0.1f, chunkSize * 0.5f);
}
public void ApplyRenderData(ChunkRenderSnapshot renderSnapshot)
{
RenderSnapshot = renderSnapshot;
}
public void ApplyColliderMesh(Mesh colliderMesh)
{
if (ColliderMesh != null)
{
DestroyMesh(ColliderMesh);
}
ColliderMesh = colliderMesh;
MountainCollider.sharedMesh = null;
MountainCollider.sharedMesh = ColliderMesh != null && ColliderMesh.vertexCount > 0 ? ColliderMesh : null;
}
public void Dispose()
{
if (Root != null)
{
if (Application.isPlaying)
{
Object.Destroy(Root.gameObject);
}
else
{
Object.DestroyImmediate(Root.gameObject);
}
}
DestroyMesh(ColliderMesh);
RenderSnapshot = null;
}
private static void DestroyMesh(Mesh mesh)
{
if (mesh == null)
{
return;
}
if (Application.isPlaying)
{
Object.Destroy(mesh);
}
else
{
Object.DestroyImmediate(mesh);
}
}
}
private sealed class RegionRuntime
{
public Transform Root;
public MeshFilter Filter;
public MeshRenderer Renderer;
public Mesh RenderMesh;
public void EnsureCreated(Vector2Int regionCoord, Transform parent, int chunkSize, int regionSizeInChunks, Material material)
{
if (Root == null)
{
GameObject regionObject = new GameObject($"VoxelRegion_{regionCoord.x}_{regionCoord.y}");
regionObject.transform.SetParent(parent, false);
Root = regionObject.transform;
Filter = regionObject.AddComponent<MeshFilter>();
Renderer = regionObject.AddComponent<MeshRenderer>();
}
Root.localPosition = new Vector3(regionCoord.x * regionSizeInChunks * chunkSize, 0f, regionCoord.y * regionSizeInChunks * chunkSize);
Renderer.sharedMaterial = material;
}
public void ApplyMesh(Mesh mesh)
{
DestroyMesh(RenderMesh);
RenderMesh = mesh;
Filter.sharedMesh = RenderMesh;
}
public void Dispose()
{
if (Root != null)
{
if (Application.isPlaying)
{
Object.Destroy(Root.gameObject);
}
else
{
Object.DestroyImmediate(Root.gameObject);
}
}
DestroyMesh(RenderMesh);
}
private static void DestroyMesh(Mesh mesh)
{
if (mesh == null)
{
return;
}
if (Application.isPlaying)
{
Object.Destroy(mesh);
}
else
{
Object.DestroyImmediate(mesh);
}
}
}
private enum ChunkState
{
None,
Generating,
SyncBuilding,
ReadyToRender,
Rendered
}
}
}
@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 56e0b6ddd79410b40bdda73c4e20515d
@@ -1,889 +0,0 @@
using System;
using System.Collections.Generic;
using Cysharp.Threading.Tasks;
using UnityEngine;
namespace InfiniteWorld.VoxelWorld
{
public sealed partial class VoxelWorldGenerator : MonoBehaviour
{
[Header("References")]
[SerializeField] private Transform streamTarget;
[SerializeField] private VoxelWorldConfig config;
private readonly Dictionary<Vector2Int, ChunkRuntime> chunks = new Dictionary<Vector2Int, ChunkRuntime>();
private readonly Dictionary<Vector2Int, RegionRuntime> regions = new Dictionary<Vector2Int, RegionRuntime>();
private readonly Dictionary<Vector2Int, int> regionVersions = new Dictionary<Vector2Int, int>();
private readonly Queue<ChunkBuildResult> completedBuilds = new Queue<ChunkBuildResult>();
private readonly Queue<RegionBuildResult> completedRegionBuilds = new Queue<RegionBuildResult>();
private readonly Queue<PendingColliderMeshApply> pendingColliderApplies = new Queue<PendingColliderMeshApply>();
private readonly Queue<Vector2Int> dirtyChunkMeshes = new Queue<Vector2Int>();
private readonly HashSet<Vector2Int> queuedChunkMeshes = new HashSet<Vector2Int>();
private readonly Queue<Vector2Int> dirtyRegions = new Queue<Vector2Int>();
private readonly HashSet<Vector2Int> queuedRegions = new HashSet<Vector2Int>();
private readonly Queue<Vector2Int> pendingNeighborRefreshes = new Queue<Vector2Int>();
private readonly HashSet<Vector2Int> queuedNeighborRefreshes = new HashSet<Vector2Int>();
private readonly object generationLock = new object();
private readonly object regionBuildLock = new object();
private Transform chunkRoot;
private Transform regionRoot;
private Vector2Int lastGeneratedCenter = new Vector2Int(int.MinValue, int.MinValue);
private int activeGenerationJobs;
private int nextChunkRuntimeId;
private int generationSession;
private int regionRebuildSession;
private VoxelWorldAtlas atlas;
private int atlasBiomeCount;
private bool regionRebuildLoopRunning;
private VoxelWorldResolvedSettings settings = VoxelWorldResolvedSettings.Default;
private int chunkSize => settings.ChunkSize;
private int generationRadius => settings.GenerationRadius;
private int blockingGenerationRadius => settings.BlockingGenerationRadius;
private int seed => settings.Seed;
private int maxMountainHeight => settings.MaxMountainHeight;
private float macroNoiseScale => settings.MacroNoiseScale;
private float detailNoiseScale => settings.DetailNoiseScale;
private float ridgeNoiseScale => settings.RidgeNoiseScale;
private float wallThreshold => settings.WallThreshold;
private float rockBias => settings.RockBias;
private int smoothingPasses => settings.SmoothingPasses;
private float passNoiseScale => settings.PassNoiseScale;
private float passDetailScale => settings.PassDetailScale;
private float passThreshold => settings.PassThreshold;
private float passFeather => settings.PassFeather;
private float heightNoiseScale => settings.HeightNoiseScale;
private float terraceNoiseScale => settings.TerraceNoiseScale;
private float heightBias => settings.HeightBias;
private IReadOnlyList<VoxelBiomeProfile> biomeProfiles => settings.BiomeProfiles;
private float biomeNoiseScale => settings.BiomeNoiseScale;
private float biomeSize => settings.BiomeSize;
private int maxAsyncChunkJobs => settings.MaxAsyncChunkJobs;
private int maxChunkBuildsPerFrame => settings.MaxChunkBuildsPerFrame;
private int maxChunkMeshBuildsPerFrame => settings.MaxChunkMeshBuildsPerFrame;
private int maxColliderAppliesPerFrame => settings.MaxColliderAppliesPerFrame;
private int maxNeighborRefreshesPerFrame => settings.MaxNeighborRefreshesPerFrame;
private int renderRegionSizeInChunks => settings.RenderRegionSizeInChunks;
private int maxRegionBuildsPerFrame => settings.MaxRegionBuildsPerFrame;
private void Awake()
{
generationSession++;
regionRebuildSession++;
EnsureRuntimeData();
EnsureChunkRoot();
EnsureRegionRoot();
TryResolveStreamTarget();
}
private void Update()
{
EnsureRuntimeData();
EnsureChunkRoot();
EnsureRegionRoot();
if (!TryResolveStreamTarget())
{
return;
}
Vector2Int centerChunk = WorldToChunk(streamTarget.position);
if (centerChunk != lastGeneratedCenter)
{
lastGeneratedCenter = centerChunk;
UnloadDistantChunks(centerChunk);
}
DrainCompletedBuilds(maxChunkBuildsPerFrame);
DrainDirtyChunkMeshes(maxChunkMeshBuildsPerFrame);
DrainPendingColliderApplies(maxColliderAppliesPerFrame);
DrainNeighborRefreshes(maxNeighborRefreshesPerFrame);
DrainCompletedRegionBuilds(maxRegionBuildsPerFrame);
ScheduleChunkGeneration(centerChunk);
EnsureBlockingChunksLoaded(centerChunk);
}
private void OnDisable()
{
generationSession++;
regionRebuildSession++;
lock (generationLock)
{
completedBuilds.Clear();
activeGenerationJobs = 0;
}
lock (regionBuildLock)
{
completedRegionBuilds.Clear();
}
CleanupChunks();
CleanupRegions();
atlas?.Dispose();
atlas = null;
}
private void EnsureRuntimeData()
{
settings = VoxelWorldResolvedSettings.Resolve(config);
int configuredBiomeCount = CountConfiguredBiomes();
if (atlas != null && atlasBiomeCount == configuredBiomeCount)
{
return;
}
atlas?.Dispose();
atlas = VoxelWorldAtlas.CreateRuntimeAtlas(biomeProfiles);
atlasBiomeCount = atlas.BiomeCount;
RefreshRegionMaterials();
}
private void EnsureChunkRoot()
{
if (chunkRoot != null)
{
return;
}
Transform existing = transform.Find("VoxelChunks");
if (existing != null)
{
chunkRoot = existing;
return;
}
GameObject root = new GameObject("VoxelChunks");
root.transform.SetParent(transform, false);
chunkRoot = root.transform;
}
private void EnsureRegionRoot()
{
if (regionRoot != null)
{
return;
}
Transform existing = transform.Find("VoxelRenderRegions");
if (existing != null)
{
regionRoot = existing;
return;
}
GameObject root = new GameObject("VoxelRenderRegions");
root.transform.SetParent(transform, false);
regionRoot = root.transform;
}
private void RefreshRegionMaterials()
{
if (atlas == null)
{
return;
}
foreach (KeyValuePair<Vector2Int, RegionRuntime> pair in regions)
{
if (pair.Value.Renderer != null)
{
pair.Value.Renderer.sharedMaterial = atlas.Material;
}
}
}
private bool TryResolveStreamTarget()
{
if (streamTarget != null)
{
return true;
}
Camera mainCamera = Camera.main;
if (mainCamera == null)
{
return false;
}
streamTarget = mainCamera.transform;
return true;
}
private void ScheduleChunkGeneration(Vector2Int centerChunk)
{
List<Vector2Int> coords = GetCoordsByPriority(centerChunk, generationRadius);
for (int i = 0; i < coords.Count; i++)
{
Vector2Int coord = coords[i];
ChunkRuntime runtime = GetOrCreateChunkRuntime(coord);
if (runtime.IsRendered || runtime.HasData || runtime.State == ChunkState.Generating)
{
continue;
}
if (IsWithinRadius(coord, centerChunk, blockingGenerationRadius))
{
continue;
}
if (!TryReserveGenerationSlot())
{
break;
}
runtime.State = ChunkState.Generating;
runtime.Version++;
GenerateChunkDataAsync(coord, runtime.Version, generationSession, runtime.RuntimeId).Forget();
}
}
private void EnsureBlockingChunksLoaded(Vector2Int centerChunk)
{
List<Vector2Int> requiredCoords = GetCoordsByPriority(centerChunk, blockingGenerationRadius);
for (int i = 0; i < requiredCoords.Count; i++)
{
Vector2Int coord = requiredCoords[i];
ChunkRuntime runtime = GetOrCreateChunkRuntime(coord);
if (runtime.IsRendered || runtime.HasData || runtime.State == ChunkState.Generating || runtime.State == ChunkState.SyncBuilding)
{
continue;
}
runtime.Version++;
runtime.State = ChunkState.SyncBuilding;
if (ApplyBuildResult(GenerateChunkData(coord, runtime.Version, generationSession, runtime.RuntimeId)))
{
EnqueueChunkMeshBuild(coord);
QueueNeighborRefresh(coord);
}
}
}
private void UnloadDistantChunks(Vector2Int centerChunk)
{
List<Vector2Int> coordsToRemove = new List<Vector2Int>();
foreach (KeyValuePair<Vector2Int, ChunkRuntime> pair in chunks)
{
if (IsWithinRadius(pair.Key, centerChunk, generationRadius))
{
continue;
}
coordsToRemove.Add(pair.Key);
}
for (int i = 0; i < coordsToRemove.Count; i++)
{
Vector2Int coord = coordsToRemove[i];
if (!chunks.TryGetValue(coord, out ChunkRuntime runtime))
{
continue;
}
MarkRegionDirty(coord);
chunks.Remove(coord);
runtime.Dispose();
QueueNeighborRefresh(coord);
}
}
private Vector2Int WorldToChunk(Vector3 position)
{
return new Vector2Int(
Mathf.FloorToInt(position.x / chunkSize),
Mathf.FloorToInt(position.z / chunkSize));
}
private async UniTaskVoid GenerateChunkDataAsync(Vector2Int coord, int version, int session, int runtimeId)
{
try
{
ChunkBuildResult result = await UniTask.RunOnThreadPool(() => GenerateChunkData(coord, version, session, runtimeId));
lock (generationLock)
{
if (session == generationSession)
{
completedBuilds.Enqueue(result);
activeGenerationJobs = Mathf.Max(0, activeGenerationJobs - 1);
}
}
}
catch (Exception exception)
{
lock (generationLock)
{
if (session == generationSession)
{
activeGenerationJobs = Mathf.Max(0, activeGenerationJobs - 1);
}
}
if (session == generationSession && chunks.TryGetValue(coord, out ChunkRuntime runtime) && runtime.Version == version && runtime.RuntimeId == runtimeId)
{
runtime.State = ChunkState.None;
}
Debug.LogException(exception, this);
}
}
private void DrainCompletedBuilds(int maxBuilds)
{
int builds = 0;
while (builds < maxBuilds)
{
ChunkBuildResult result;
lock (generationLock)
{
if (completedBuilds.Count == 0)
{
break;
}
result = completedBuilds.Dequeue();
}
if (!ApplyBuildResult(result))
{
continue;
}
EnqueueChunkMeshBuild(result.Coord);
QueueNeighborRefresh(result.Coord);
builds++;
}
}
private void DrainDirtyChunkMeshes(int maxBuilds)
{
int builds = 0;
while (builds < maxBuilds)
{
if (dirtyChunkMeshes.Count == 0)
{
break;
}
Vector2Int coord = dirtyChunkMeshes.Dequeue();
queuedChunkMeshes.Remove(coord);
if (!chunks.TryGetValue(coord, out ChunkRuntime runtime) || !runtime.HasData)
{
continue;
}
RenderChunk(coord);
builds++;
}
}
private void EnqueueChunkMeshBuild(Vector2Int coord)
{
if (!queuedChunkMeshes.Add(coord))
{
return;
}
dirtyChunkMeshes.Enqueue(coord);
}
private void EnqueueColliderApply(Vector2Int coord, int version, int runtimeId, Mesh colliderMesh)
{
pendingColliderApplies.Enqueue(new PendingColliderMeshApply(coord, version, runtimeId, colliderMesh));
}
private void DrainPendingColliderApplies(int maxApplies)
{
int applies = 0;
while (applies < maxApplies)
{
if (pendingColliderApplies.Count == 0)
{
break;
}
PendingColliderMeshApply pending = pendingColliderApplies.Dequeue();
if (!chunks.TryGetValue(pending.Coord, out ChunkRuntime runtime) || runtime.Version != pending.Version || runtime.RuntimeId != pending.RuntimeId)
{
DestroyMeshAsset(pending.ColliderMesh);
applies++;
continue;
}
runtime.ApplyColliderMesh(pending.ColliderMesh);
applies++;
}
}
private void QueueNeighborRefresh(Vector2Int coord)
{
if (!queuedNeighborRefreshes.Add(coord))
{
return;
}
pendingNeighborRefreshes.Enqueue(coord);
}
private void DrainNeighborRefreshes(int maxRefreshes)
{
int refreshes = 0;
while (refreshes < maxRefreshes)
{
if (pendingNeighborRefreshes.Count == 0)
{
break;
}
Vector2Int coord = pendingNeighborRefreshes.Dequeue();
queuedNeighborRefreshes.Remove(coord);
RefreshNeighborBorders(coord);
refreshes++;
}
}
private void RefreshNeighborBorders(Vector2Int coord)
{
TryRenderNeighbor(coord + Vector2Int.up);
TryRenderNeighbor(coord + Vector2Int.right);
TryRenderNeighbor(coord + Vector2Int.down);
TryRenderNeighbor(coord + Vector2Int.left);
}
private void TryRenderNeighbor(Vector2Int coord)
{
if (!chunks.TryGetValue(coord, out ChunkRuntime runtime) || !runtime.HasData)
{
return;
}
EnqueueChunkMeshBuild(coord);
}
private void MarkRegionDirty(Vector2Int chunkCoord)
{
Vector2Int regionCoord = ChunkToRegion(chunkCoord);
regionVersions.TryGetValue(regionCoord, out int version);
regionVersions[regionCoord] = version + 1;
if (!queuedRegions.Add(regionCoord))
{
return;
}
dirtyRegions.Enqueue(regionCoord);
EnsureRegionRebuildLoop();
}
private void EnsureRegionRebuildLoop()
{
if (regionRebuildLoopRunning)
{
return;
}
regionRebuildLoopRunning = true;
ProcessDirtyRegionsAsync(regionRebuildSession).Forget();
}
private async UniTaskVoid ProcessDirtyRegionsAsync(int session)
{
try
{
while (dirtyRegions.Count > 0)
{
if (!this || !isActiveAndEnabled || session != regionRebuildSession)
{
break;
}
int buildsThisFrame = 0;
while (buildsThisFrame < maxRegionBuildsPerFrame && dirtyRegions.Count > 0)
{
Vector2Int regionCoord = dirtyRegions.Dequeue();
queuedRegions.Remove(regionCoord);
if (!TryCaptureRegionBuildRequest(regionCoord, out RegionBuildRequest request))
{
buildsThisFrame++;
continue;
}
RegionBuildResult result = await UniTask.RunOnThreadPool(() => BuildRegionBuffers(request));
lock (regionBuildLock)
{
completedRegionBuilds.Enqueue(result);
}
buildsThisFrame++;
}
if (dirtyRegions.Count > 0)
{
await UniTask.Yield(PlayerLoopTiming.Update);
}
}
}
finally
{
if (session == regionRebuildSession)
{
regionRebuildLoopRunning = false;
if (dirtyRegions.Count > 0)
{
EnsureRegionRebuildLoop();
}
}
}
}
private bool TryCaptureRegionBuildRequest(Vector2Int regionCoord, out RegionBuildRequest request)
{
if (atlas == null || regionRoot == null)
{
request = new RegionBuildRequest();
return false;
}
Vector2Int baseChunk = new Vector2Int(regionCoord.x * renderRegionSizeInChunks, regionCoord.y * renderRegionSizeInChunks);
List<RegionChunkSnapshot> chunkSnapshots = new List<RegionChunkSnapshot>(renderRegionSizeInChunks * renderRegionSizeInChunks);
for (int z = 0; z < renderRegionSizeInChunks; z++)
{
for (int x = 0; x < renderRegionSizeInChunks; x++)
{
Vector2Int chunkCoord = new Vector2Int(baseChunk.x + x, baseChunk.y + z);
if (!chunks.TryGetValue(chunkCoord, out ChunkRuntime runtime) || runtime.RenderSnapshot == null || runtime.RenderSnapshot.IsEmpty)
{
continue;
}
chunkSnapshots.Add(new RegionChunkSnapshot(runtime.RenderSnapshot, new Vector3(x * chunkSize, 0f, z * chunkSize)));
}
}
regionVersions.TryGetValue(regionCoord, out int version);
request = new RegionBuildRequest(regionCoord, version, generationSession, chunkSnapshots.ToArray());
return true;
}
private Vector2Int ChunkToRegion(Vector2Int chunkCoord)
{
return new Vector2Int(
Mathf.FloorToInt(chunkCoord.x / (float)renderRegionSizeInChunks),
Mathf.FloorToInt(chunkCoord.y / (float)renderRegionSizeInChunks));
}
private RegionRuntime GetOrCreateRegionRuntime(Vector2Int regionCoord)
{
if (!regions.TryGetValue(regionCoord, out RegionRuntime region))
{
region = new RegionRuntime();
regions.Add(regionCoord, region);
}
return region;
}
private static RegionBuildResult BuildRegionBuffers(RegionBuildRequest request)
{
if (request.Chunks == null || request.Chunks.Length == 0)
{
return RegionBuildResult.CreateEmpty(request.RegionCoord, request.Version, request.Session);
}
int totalVertexCount = 0;
int totalTriangleCount = 0;
for (int i = 0; i < request.Chunks.Length; i++)
{
ChunkRenderSnapshot snapshot = request.Chunks[i].Snapshot;
totalVertexCount += snapshot.Vertices.Length;
totalTriangleCount += snapshot.Triangles.Length;
}
if (totalVertexCount == 0 || totalTriangleCount == 0)
{
return RegionBuildResult.CreateEmpty(request.RegionCoord, request.Version, request.Session);
}
Vector3[] vertices = new Vector3[totalVertexCount];
Vector3[] normals = new Vector3[totalVertexCount];
Vector2[] uv0 = new Vector2[totalVertexCount];
Vector2[] uv1 = new Vector2[totalVertexCount];
int[] triangles = new int[totalTriangleCount];
int vertexOffset = 0;
int triangleOffset = 0;
Vector3 min = new Vector3(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity);
Vector3 max = new Vector3(float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity);
for (int chunkIndex = 0; chunkIndex < request.Chunks.Length; chunkIndex++)
{
RegionChunkSnapshot chunk = request.Chunks[chunkIndex];
ChunkRenderSnapshot snapshot = chunk.Snapshot;
Vector3 offset = chunk.LocalOffset;
for (int vertexIndex = 0; vertexIndex < snapshot.Vertices.Length; vertexIndex++)
{
Vector3 vertex = snapshot.Vertices[vertexIndex] + offset;
int writeIndex = vertexOffset + vertexIndex;
vertices[writeIndex] = vertex;
normals[writeIndex] = snapshot.Normals[vertexIndex];
uv0[writeIndex] = snapshot.Uv0[vertexIndex];
uv1[writeIndex] = snapshot.Uv1[vertexIndex];
min = Vector3.Min(min, vertex);
max = Vector3.Max(max, vertex);
}
for (int triangleIndex = 0; triangleIndex < snapshot.Triangles.Length; triangleIndex++)
{
triangles[triangleOffset + triangleIndex] = snapshot.Triangles[triangleIndex] + vertexOffset;
}
vertexOffset += snapshot.Vertices.Length;
triangleOffset += snapshot.Triangles.Length;
}
Bounds bounds = new Bounds((min + max) * 0.5f, max - min);
return new RegionBuildResult(request.RegionCoord, request.Version, request.Session, vertices, normals, uv0, uv1, triangles, bounds);
}
private void DrainCompletedRegionBuilds(int maxBuilds)
{
int builds = 0;
while (builds < maxBuilds)
{
RegionBuildResult result;
lock (regionBuildLock)
{
if (completedRegionBuilds.Count == 0)
{
break;
}
result = completedRegionBuilds.Dequeue();
}
ApplyRegionBuildResult(result);
builds++;
}
}
private void ApplyRegionBuildResult(RegionBuildResult result)
{
if (result.Session != generationSession || atlas == null || regionRoot == null)
{
return;
}
regionVersions.TryGetValue(result.RegionCoord, out int currentVersion);
if (result.Version != currentVersion)
{
return;
}
if (result.IsEmpty)
{
if (regions.TryGetValue(result.RegionCoord, out RegionRuntime regionToRemove))
{
regionToRemove.Dispose();
regions.Remove(result.RegionCoord);
}
regionVersions.Remove(result.RegionCoord);
return;
}
RegionRuntime region = GetOrCreateRegionRuntime(result.RegionCoord);
region.EnsureCreated(result.RegionCoord, regionRoot, chunkSize, renderRegionSizeInChunks, atlas.Material);
Mesh mesh = new Mesh { name = $"Region_{result.RegionCoord.x}_{result.RegionCoord.y}" };
mesh.indexFormat = result.Vertices.Length > 65535 ? UnityEngine.Rendering.IndexFormat.UInt32 : UnityEngine.Rendering.IndexFormat.UInt16;
mesh.vertices = result.Vertices;
mesh.normals = result.Normals;
mesh.uv = result.Uv0;
mesh.uv2 = result.Uv1;
mesh.triangles = result.Triangles;
mesh.bounds = result.Bounds;
region.ApplyMesh(mesh);
}
private bool ApplyBuildResult(ChunkBuildResult result)
{
if (result.Session != generationSession)
{
return false;
}
if (!chunks.TryGetValue(result.Coord, out ChunkRuntime runtime))
{
return false;
}
if (runtime.Version != result.Version)
{
return false;
}
if (runtime.RuntimeId != result.RuntimeId)
{
return false;
}
runtime.Heights = result.Heights;
runtime.BiomeIndices = result.BiomeIndices;
if (!runtime.IsRendered)
{
runtime.State = ChunkState.ReadyToRender;
}
return true;
}
private bool TryReserveGenerationSlot()
{
lock (generationLock)
{
if (activeGenerationJobs >= maxAsyncChunkJobs)
{
return false;
}
activeGenerationJobs++;
return true;
}
}
private List<Vector2Int> GetCoordsByPriority(Vector2Int centerChunk, int radius)
{
List<Vector2Int> coords = new List<Vector2Int>((radius * 2 + 1) * (radius * 2 + 1));
for (int z = -radius; z <= radius; z++)
{
for (int x = -radius; x <= radius; x++)
{
coords.Add(new Vector2Int(centerChunk.x + x, centerChunk.y + z));
}
}
coords.Sort((left, right) => CompareChunkPriority(centerChunk, left, right));
return coords;
}
private static int CompareChunkPriority(Vector2Int centerChunk, Vector2Int left, Vector2Int right)
{
int leftDx = Mathf.Abs(left.x - centerChunk.x);
int leftDz = Mathf.Abs(left.y - centerChunk.y);
int rightDx = Mathf.Abs(right.x - centerChunk.x);
int rightDz = Mathf.Abs(right.y - centerChunk.y);
int leftChebyshev = Mathf.Max(leftDx, leftDz);
int rightChebyshev = Mathf.Max(rightDx, rightDz);
int chebyshevCompare = leftChebyshev.CompareTo(rightChebyshev);
if (chebyshevCompare != 0)
{
return chebyshevCompare;
}
int leftDistance = leftDx * leftDx + leftDz * leftDz;
int rightDistance = rightDx * rightDx + rightDz * rightDz;
int distanceCompare = leftDistance.CompareTo(rightDistance);
if (distanceCompare != 0)
{
return distanceCompare;
}
int zCompare = left.y.CompareTo(right.y);
return zCompare != 0 ? zCompare : left.x.CompareTo(right.x);
}
private ChunkRuntime GetOrCreateChunkRuntime(Vector2Int coord)
{
if (!chunks.TryGetValue(coord, out ChunkRuntime runtime))
{
runtime = new ChunkRuntime();
runtime.RuntimeId = ++nextChunkRuntimeId;
chunks.Add(coord, runtime);
}
return runtime;
}
private static bool IsWithinRadius(Vector2Int coord, Vector2Int centerChunk, int radius)
{
int dx = Mathf.Abs(coord.x - centerChunk.x);
int dz = Mathf.Abs(coord.y - centerChunk.y);
return dx <= radius && dz <= radius;
}
private void CleanupChunks()
{
foreach (KeyValuePair<Vector2Int, ChunkRuntime> pair in chunks)
{
pair.Value.Dispose();
}
chunks.Clear();
dirtyChunkMeshes.Clear();
queuedChunkMeshes.Clear();
pendingNeighborRefreshes.Clear();
queuedNeighborRefreshes.Clear();
while (pendingColliderApplies.Count > 0)
{
DestroyMeshAsset(pendingColliderApplies.Dequeue().ColliderMesh);
}
}
private static void DestroyMeshAsset(Mesh mesh)
{
if (mesh == null)
{
return;
}
if (Application.isPlaying)
{
UnityEngine.Object.Destroy(mesh);
}
else
{
UnityEngine.Object.DestroyImmediate(mesh);
}
}
private void CleanupRegions()
{
foreach (KeyValuePair<Vector2Int, RegionRuntime> pair in regions)
{
pair.Value.Dispose();
}
regions.Clear();
regionVersions.Clear();
dirtyRegions.Clear();
queuedRegions.Clear();
lock (regionBuildLock)
{
completedRegionBuilds.Clear();
}
regionRebuildLoopRunning = false;
}
private int CountConfiguredBiomes()
{
int count = 0;
for (int i = 0; i < biomeProfiles.Count; i++)
{
if (biomeProfiles[i] != null)
{
count++;
}
}
return Mathf.Max(1, count);
}
}
}
@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 62cd563780165844caddc098f92ff23f
-8
View File
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 96adefd3df6641c40b9624eb12185ea4
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
-232
View File
@@ -1,232 +0,0 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace InfiniteWorld
{
[CreateAssetMenu(menuName = "Infinite World/Chunk Template", fileName = "ChunkTemplate")]
public class ChunkTemplate : ScriptableObject
{
[Min(4)] public int width = 16;
[Min(4)] public int height = 16;
[Header("Exits")]
public bool exitTop = true;
public bool exitRight = true;
public bool exitBottom = true;
public bool exitLeft = true;
[SerializeField] private List<ChunkCellData> cells = new List<ChunkCellData>();
public int CellCount => width * height;
public void EnsureCellData()
{
int target = Mathf.Max(1, width * height);
while (cells.Count < target)
{
cells.Add(new ChunkCellData());
}
while (cells.Count > target)
{
cells.RemoveAt(cells.Count - 1);
}
}
public void Resize(int newWidth, int newHeight)
{
newWidth = Mathf.Max(4, newWidth);
newHeight = Mathf.Max(4, newHeight);
List<ChunkCellData> oldCells = new List<ChunkCellData>(cells);
int oldWidth = width;
int oldHeight = height;
width = newWidth;
height = newHeight;
cells = new List<ChunkCellData>(newWidth * newHeight);
for (int i = 0; i < newWidth * newHeight; i++)
{
cells.Add(new ChunkCellData());
}
for (int y = 0; y < Mathf.Min(oldHeight, newHeight); y++)
{
for (int x = 0; x < Mathf.Min(oldWidth, newWidth); x++)
{
int oldIndex = y * oldWidth + x;
if (oldIndex < oldCells.Count)
{
cells[Index(x, y)] = oldCells[oldIndex];
}
}
}
}
public void Clear()
{
EnsureCellData();
for (int i = 0; i < cells.Count; i++)
{
cells[i] = new ChunkCellData();
}
}
public bool GetWall(int x, int y)
{
return IsInBounds(x, y) && cells[Index(x, y)].wall;
}
public bool GetEnvironment(int x, int y)
{
return IsInBounds(x, y) && cells[Index(x, y)].environment;
}
public void SetWall(int x, int y, bool value)
{
if (!IsInBounds(x, y))
{
return;
}
EnsureCellData();
ChunkCellData data = cells[Index(x, y)];
data.wall = value;
if (value)
{
data.environment = false;
}
cells[Index(x, y)] = data;
}
public void SetEnvironment(int x, int y, bool value)
{
if (!IsInBounds(x, y))
{
return;
}
EnsureCellData();
ChunkCellData data = cells[Index(x, y)];
data.environment = value;
if (value)
{
data.wall = false;
}
cells[Index(x, y)] = data;
}
public bool GetExit(ChunkExit exit)
{
return exit switch
{
ChunkExit.Top => exitTop,
ChunkExit.Right => exitRight,
ChunkExit.Bottom => exitBottom,
ChunkExit.Left => exitLeft,
_ => false
};
}
public int ExitCount()
{
int count = 0;
count += exitTop ? 1 : 0;
count += exitRight ? 1 : 0;
count += exitBottom ? 1 : 0;
count += exitLeft ? 1 : 0;
return count;
}
public void ApplyBorderWallsFromExits(int openingWidth = 3)
{
EnsureCellData();
Clear();
for (int x = 0; x < width; x++)
{
SetWall(x, 0, true);
SetWall(x, height - 1, true);
}
for (int y = 0; y < height; y++)
{
SetWall(0, y, true);
SetWall(width - 1, y, true);
}
CarveExit(ChunkExit.Top, openingWidth);
CarveExit(ChunkExit.Right, openingWidth);
CarveExit(ChunkExit.Bottom, openingWidth);
CarveExit(ChunkExit.Left, openingWidth);
}
public void CarveExit(ChunkExit exit, int openingWidth = 3)
{
if (!GetExit(exit))
{
return;
}
int half = Mathf.Max(1, openingWidth) / 2;
switch (exit)
{
case ChunkExit.Top:
for (int x = width / 2 - half; x <= width / 2 + half; x++)
{
SetWall(x, height - 1, false);
SetWall(x, height - 2, false);
}
break;
case ChunkExit.Right:
for (int y = height / 2 - half; y <= height / 2 + half; y++)
{
SetWall(width - 1, y, false);
SetWall(width - 2, y, false);
}
break;
case ChunkExit.Bottom:
for (int x = width / 2 - half; x <= width / 2 + half; x++)
{
SetWall(x, 0, false);
SetWall(x, 1, false);
}
break;
case ChunkExit.Left:
for (int y = height / 2 - half; y <= height / 2 + half; y++)
{
SetWall(0, y, false);
SetWall(1, y, false);
}
break;
}
}
private int Index(int x, int y)
{
return y * width + x;
}
private bool IsInBounds(int x, int y)
{
return x >= 0 && y >= 0 && x < width && y < height;
}
}
public enum ChunkExit
{
Top,
Right,
Bottom,
Left
}
[Serializable]
public struct ChunkCellData
{
public bool wall;
public bool environment;
}
}
@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: bae8eeae2da7d3f4396883671b297a47
File diff suppressed because it is too large Load Diff
@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 6bacd3a0ac13e6f4a94548426dd89ebb
@@ -1,350 +0,0 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Tilemaps;
namespace InfiniteWorld
{
public static class ProceduralWorldArt
{
private const int TextureSize = 32;
private static readonly Dictionary<string, Sprite> SpriteCache = new Dictionary<string, Sprite>();
private static readonly Dictionary<string, Tile> TileCache = new Dictionary<string, Tile>();
public static Sprite CreatePlayerSprite()
{
const string key = "player_sprite";
if (SpriteCache.TryGetValue(key, out Sprite cached))
{
return cached;
}
Color transparent = new Color(0f, 0f, 0f, 0f);
Texture2D texture = CreateTexture(key, transparent);
Color cloak = new Color(0.13f, 0.33f, 0.79f, 1f);
Color trim = new Color(0.85f, 0.92f, 1f, 1f);
Color face = new Color(0.99f, 0.89f, 0.72f, 1f);
FillRect(texture, 10, 4, 12, 16, cloak);
FillRect(texture, 9, 18, 14, 7, cloak);
FillRect(texture, 12, 24, 8, 5, face);
FillRect(texture, 11, 23, 10, 1, trim);
FillRect(texture, 8, 12, 3, 10, trim);
FillRect(texture, 21, 12, 3, 10, trim);
FillRect(texture, 12, 0, 3, 4, cloak);
FillRect(texture, 17, 0, 3, 4, cloak);
texture.Apply();
Sprite sprite = CreateSprite(texture);
SpriteCache[key] = sprite;
return sprite;
}
public static TileBase CreateSolidTile(string key, Color color, float noiseStrength = 0.04f)
{
if (TileCache.TryGetValue(key, out Tile cached))
{
return cached;
}
Texture2D texture = CreateTexture(key, color);
ApplyNoise(texture, noiseStrength, 31);
texture.Apply();
Tile tile = CreateTile(texture, key);
TileCache[key] = tile;
return tile;
}
public static TileBase CreateFeatureTile(string key, Color fill, Color border, bool top, bool right, bool bottom, bool left, InnerCornerMask innerCornerMask = InnerCornerMask.None, bool solidCollider = false)
{
if (TileCache.TryGetValue(key, out Tile cached))
{
return cached;
}
Texture2D texture = CreateTexture(key, fill);
ApplyNoise(texture, 0.05f, 71);
DrawBorder(texture, top, right, bottom, left, border, 4);
CarveInnerCorner(texture, innerCornerMask);
texture.Apply();
Tile tile = CreateTile(texture, key);
if (solidCollider)
{
tile.colliderType = Tile.ColliderType.Grid;
}
TileCache[key] = tile;
return tile;
}
public static TileBase CreateDecorationTile(string key, Color baseColor, Color accentColor, DecorationPattern pattern)
{
if (TileCache.TryGetValue(key, out Tile cached))
{
return cached;
}
Color transparent = new Color(0f, 0f, 0f, 0f);
Texture2D texture = CreateTexture(key, transparent);
switch (pattern)
{
case DecorationPattern.Bush:
DrawDisc(texture, 16, 15, 8, baseColor);
DrawDisc(texture, 10, 13, 6, baseColor);
DrawDisc(texture, 22, 13, 6, baseColor);
DrawDisc(texture, 14, 17, 3, accentColor);
DrawDisc(texture, 21, 17, 3, accentColor);
break;
case DecorationPattern.Flower:
DrawDisc(texture, 16, 12, 2, baseColor);
DrawDisc(texture, 12, 16, 2, baseColor);
DrawDisc(texture, 20, 16, 2, baseColor);
DrawDisc(texture, 16, 20, 2, baseColor);
DrawDisc(texture, 16, 16, 2, accentColor);
DrawLine(texture, 16, 0, 16, 12, new Color(0.2f, 0.5f, 0.2f, 1f));
break;
default:
DrawDisc(texture, 12, 12, 4, baseColor);
DrawDisc(texture, 20, 18, 3, accentColor);
DrawDisc(texture, 9, 20, 2, accentColor);
break;
}
texture.Apply();
Tile tile = CreateTile(texture, key);
TileCache[key] = tile;
return tile;
}
private static Texture2D CreateTexture(string key, Color fillColor)
{
Texture2D texture = new Texture2D(TextureSize, TextureSize, TextureFormat.RGBA32, false)
{
filterMode = FilterMode.Point,
wrapMode = TextureWrapMode.Clamp,
name = key
};
Color[] pixels = new Color[TextureSize * TextureSize];
for (int i = 0; i < pixels.Length; i++)
{
pixels[i] = fillColor;
}
texture.SetPixels(pixels);
return texture;
}
private static Tile CreateTile(Texture2D texture, string key)
{
Sprite sprite = CreateSprite(texture);
Tile tile = ScriptableObject.CreateInstance<Tile>();
tile.sprite = sprite;
tile.name = key;
tile.hideFlags = HideFlags.HideAndDontSave;
return tile;
}
private static Sprite CreateSprite(Texture2D texture)
{
Sprite sprite = Sprite.Create(texture, new Rect(0f, 0f, texture.width, texture.height), new Vector2(0.5f, 0.5f), TextureSize);
sprite.name = texture.name;
return sprite;
}
private static void ApplyNoise(Texture2D texture, float strength, int offset)
{
if (strength <= 0f)
{
return;
}
for (int y = 0; y < texture.height; y++)
{
for (int x = 0; x < texture.width; x++)
{
Color pixel = texture.GetPixel(x, y);
if (pixel.a <= 0f)
{
continue;
}
float noise = Mathf.PerlinNoise((x + offset) * 0.21f, (y + offset) * 0.21f) - 0.5f;
texture.SetPixel(x, y, pixel * (1f + noise * strength * 2f));
}
}
}
private static void DrawBorder(Texture2D texture, bool top, bool right, bool bottom, bool left, Color color, int thickness)
{
if (top)
{
FillRect(texture, 0, TextureSize - thickness, TextureSize, thickness, color);
}
if (bottom)
{
FillRect(texture, 0, 0, TextureSize, thickness, color);
}
if (left)
{
FillRect(texture, 0, 0, thickness, TextureSize, color);
}
if (right)
{
FillRect(texture, TextureSize - thickness, 0, thickness, TextureSize, color);
}
}
private static void CarveInnerCorner(Texture2D texture, InnerCornerMask innerCornerMask)
{
if (innerCornerMask == InnerCornerMask.None)
{
return;
}
Color transparent = new Color(0f, 0f, 0f, 0f);
const int radius = 8;
if ((innerCornerMask & InnerCornerMask.TopLeft) != 0)
{
CutCorner(texture, 0, TextureSize - 1, radius, transparent, 1, -1);
}
if ((innerCornerMask & InnerCornerMask.TopRight) != 0)
{
CutCorner(texture, TextureSize - 1, TextureSize - 1, radius, transparent, -1, -1);
}
if ((innerCornerMask & InnerCornerMask.BottomRight) != 0)
{
CutCorner(texture, TextureSize - 1, 0, radius, transparent, -1, 1);
}
if ((innerCornerMask & InnerCornerMask.BottomLeft) != 0)
{
CutCorner(texture, 0, 0, radius, transparent, 1, 1);
}
}
private static void CutCorner(Texture2D texture, int originX, int originY, int radius, Color transparent, int dirX, int dirY)
{
for (int x = 0; x < radius; x++)
{
for (int y = 0; y < radius; y++)
{
float dx = x / (float)radius;
float dy = y / (float)radius;
if (dx * dx + dy * dy > 1f)
{
continue;
}
int px = originX + x * dirX;
int py = originY + y * dirY;
if (px >= 0 && px < TextureSize && py >= 0 && py < TextureSize)
{
texture.SetPixel(px, py, transparent);
}
}
}
}
private static void DrawDisc(Texture2D texture, int centerX, int centerY, int radius, Color color)
{
int sqrRadius = radius * radius;
for (int y = -radius; y <= radius; y++)
{
for (int x = -radius; x <= radius; x++)
{
if (x * x + y * y > sqrRadius)
{
continue;
}
int px = centerX + x;
int py = centerY + y;
if (px >= 0 && px < TextureSize && py >= 0 && py < TextureSize)
{
texture.SetPixel(px, py, color);
}
}
}
}
private static void DrawLine(Texture2D texture, int x0, int y0, int x1, int y1, Color color)
{
int dx = Mathf.Abs(x1 - x0);
int sx = x0 < x1 ? 1 : -1;
int dy = -Mathf.Abs(y1 - y0);
int sy = y0 < y1 ? 1 : -1;
int error = dx + dy;
while (true)
{
if (x0 >= 0 && x0 < TextureSize && y0 >= 0 && y0 < TextureSize)
{
texture.SetPixel(x0, y0, color);
}
if (x0 == x1 && y0 == y1)
{
break;
}
int twiceError = 2 * error;
if (twiceError >= dy)
{
error += dy;
x0 += sx;
}
if (twiceError <= dx)
{
error += dx;
y0 += sy;
}
}
}
private static void FillRect(Texture2D texture, int x, int y, int width, int height, Color color)
{
for (int py = y; py < y + height; py++)
{
if (py < 0 || py >= TextureSize)
{
continue;
}
for (int px = x; px < x + width; px++)
{
if (px < 0 || px >= TextureSize)
{
continue;
}
texture.SetPixel(px, py, color);
}
}
}
}
public enum DecorationPattern
{
Bush,
Flower,
Rocks
}
[System.Flags]
public enum InnerCornerMask
{
None = 0,
TopLeft = 1,
TopRight = 2,
BottomRight = 4,
BottomLeft = 8
}
}
@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: fc8c4a69ee2e7a046adf658709613591
@@ -1,237 +0,0 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace InfiniteWorld
{
public static class RuntimeWorldProfileFactory
{
public static WorldAutotileProfile CreateFallbackProfile()
{
WorldAutotileProfile profile = ScriptableObject.CreateInstance<WorldAutotileProfile>();
profile.name = "RuntimeFallbackWorldProfile";
Color grass = new Color(0.35f, 0.62f, 0.26f, 1f);
Color wallFill = new Color(0.48f, 0.37f, 0.22f, 1f);
Color wallBorder = new Color(0.29f, 0.21f, 0.12f, 1f);
profile.baseGroundTile = ProceduralWorldArt.CreateSolidTile("base_grass", grass, 0.06f);
profile.wallTiles.center = ProceduralWorldArt.CreateFeatureTile("wall_center", wallFill, wallBorder, false, false, false, false, InnerCornerMask.None, true);
profile.wallTiles.top = ProceduralWorldArt.CreateFeatureTile("wall_top", wallFill, wallBorder, true, false, false, false, InnerCornerMask.None, true);
profile.wallTiles.right = ProceduralWorldArt.CreateFeatureTile("wall_right", wallFill, wallBorder, false, true, false, false, InnerCornerMask.None, true);
profile.wallTiles.bottom = ProceduralWorldArt.CreateFeatureTile("wall_bottom", wallFill, wallBorder, false, false, true, false, InnerCornerMask.None, true);
profile.wallTiles.left = ProceduralWorldArt.CreateFeatureTile("wall_left", wallFill, wallBorder, false, false, false, true, InnerCornerMask.None, true);
profile.wallTiles.outerTopLeft = ProceduralWorldArt.CreateFeatureTile("wall_outer_tl", wallFill, wallBorder, true, false, false, true, InnerCornerMask.None, true);
profile.wallTiles.outerTopRight = ProceduralWorldArt.CreateFeatureTile("wall_outer_tr", wallFill, wallBorder, true, true, false, false, InnerCornerMask.None, true);
profile.wallTiles.outerBottomRight = ProceduralWorldArt.CreateFeatureTile("wall_outer_br", wallFill, wallBorder, false, true, true, false, InnerCornerMask.None, true);
profile.wallTiles.outerBottomLeft = ProceduralWorldArt.CreateFeatureTile("wall_outer_bl", wallFill, wallBorder, false, false, true, true, InnerCornerMask.None, true);
profile.wallTiles.innerTopLeft = ProceduralWorldArt.CreateFeatureTile("wall_inner_tl", wallFill, wallBorder, false, false, false, false, InnerCornerMask.TopLeft, true);
profile.wallTiles.innerTopRight = ProceduralWorldArt.CreateFeatureTile("wall_inner_tr", wallFill, wallBorder, false, false, false, false, InnerCornerMask.TopRight, true);
profile.wallTiles.innerBottomRight = ProceduralWorldArt.CreateFeatureTile("wall_inner_br", wallFill, wallBorder, false, false, false, false, InnerCornerMask.BottomRight, true);
profile.wallTiles.innerBottomLeft = ProceduralWorldArt.CreateFeatureTile("wall_inner_bl", wallFill, wallBorder, false, false, false, false, InnerCornerMask.BottomLeft, true);
profile.environmentTiles.Add(new EnvironmentTileEntry
{
id = "Bush",
tile = ProceduralWorldArt.CreateDecorationTile("env_bush", new Color(0.15f, 0.45f, 0.18f, 1f), new Color(0.23f, 0.58f, 0.25f, 1f), DecorationPattern.Bush),
weight = 1.4f
});
profile.environmentTiles.Add(new EnvironmentTileEntry
{
id = "FlowerBlue",
tile = ProceduralWorldArt.CreateDecorationTile("env_flower_blue", new Color(0.44f, 0.62f, 0.96f, 1f), new Color(0.98f, 0.91f, 0.52f, 1f), DecorationPattern.Flower),
weight = 0.75f
});
profile.environmentTiles.Add(new EnvironmentTileEntry
{
id = "Rock",
tile = ProceduralWorldArt.CreateDecorationTile("env_rock", new Color(0.48f, 0.48f, 0.52f, 1f), new Color(0.69f, 0.69f, 0.74f, 1f), DecorationPattern.Rocks),
weight = 0.9f
});
return profile;
}
public static List<ChunkTemplate> CreateFallbackTemplates(int size)
{
List<ChunkTemplate> templates = new List<ChunkTemplate>
{
CreateCross(size),
CreateStraightHorizontal(size),
CreateStraightVertical(size),
CreateCorner(size, true, true, false, false),
CreateCorner(size, false, true, true, false),
CreateCorner(size, false, false, true, true),
CreateCorner(size, true, false, false, true),
CreateTJunction(size, true, true, true, false),
CreateTJunction(size, false, true, true, true),
CreateTJunction(size, true, false, true, true),
CreateTJunction(size, true, true, false, true)
};
return templates;
}
private static ChunkTemplate CreateCross(int size)
{
ChunkTemplate template = CreateTemplate("Runtime_Cross", size, size, true, true, true, true);
DrawRoom(template, 3, 3, size - 6, size - 6);
DrawPillar(template, 4, 4);
DrawPillar(template, size - 5, 4);
DrawPillar(template, 4, size - 5);
DrawPillar(template, size - 5, size - 5);
ScatterEnvironment(template, 3);
return template;
}
private static ChunkTemplate CreateStraightHorizontal(int size)
{
ChunkTemplate template = CreateTemplate("Runtime_Straight_H", size, size, false, true, false, true);
DrawRoom(template, 2, 4, size - 4, size - 8);
DrawSideAlcoves(template, horizontal: true);
ScatterEnvironment(template, 2);
return template;
}
private static ChunkTemplate CreateStraightVertical(int size)
{
ChunkTemplate template = CreateTemplate("Runtime_Straight_V", size, size, true, false, true, false);
DrawRoom(template, 4, 2, size - 8, size - 4);
DrawSideAlcoves(template, horizontal: false);
ScatterEnvironment(template, 2);
return template;
}
private static ChunkTemplate CreateCorner(int size, bool top, bool right, bool bottom, bool left)
{
ChunkTemplate template = CreateTemplate($"Runtime_Corner_{top}_{right}_{bottom}_{left}", size, size, top, right, bottom, left);
DrawRoom(template, 3, 3, size - 6, size - 6);
if (!top)
{
DrawWallLine(template, 5, size - 5, size - 6, size - 5);
}
if (!right)
{
DrawWallLine(template, size - 5, 5, size - 5, size - 6);
}
if (!bottom)
{
DrawWallLine(template, 5, 4, size - 6, 4);
}
if (!left)
{
DrawWallLine(template, 4, 5, 4, size - 6);
}
ScatterEnvironment(template, 2);
return template;
}
private static ChunkTemplate CreateTJunction(int size, bool top, bool right, bool bottom, bool left)
{
ChunkTemplate template = CreateTemplate($"Runtime_T_{top}_{right}_{bottom}_{left}", size, size, top, right, bottom, left);
DrawRoom(template, 2, 2, size - 4, size - 4);
DrawPillar(template, size / 2, size / 2);
ScatterEnvironment(template, 3);
return template;
}
private static ChunkTemplate CreateTemplate(string name, int width, int height, bool top, bool right, bool bottom, bool left)
{
ChunkTemplate template = ScriptableObject.CreateInstance<ChunkTemplate>();
template.name = name;
template.Resize(width, height);
template.exitTop = top;
template.exitRight = right;
template.exitBottom = bottom;
template.exitLeft = left;
template.ApplyBorderWallsFromExits(3);
return template;
}
private static void DrawRoom(ChunkTemplate template, int xMin, int yMin, int width, int height)
{
for (int x = xMin; x < xMin + width; x++)
{
template.SetWall(x, yMin, true);
template.SetWall(x, yMin + height - 1, true);
}
for (int y = yMin; y < yMin + height; y++)
{
template.SetWall(xMin, y, true);
template.SetWall(xMin + width - 1, y, true);
}
template.CarveExit(ChunkExit.Top, 3);
template.CarveExit(ChunkExit.Right, 3);
template.CarveExit(ChunkExit.Bottom, 3);
template.CarveExit(ChunkExit.Left, 3);
}
private static void DrawPillar(ChunkTemplate template, int x, int y)
{
for (int py = -1; py <= 1; py++)
{
for (int px = -1; px <= 1; px++)
{
template.SetWall(x + px, y + py, true);
}
}
}
private static void DrawSideAlcoves(ChunkTemplate template, bool horizontal)
{
if (horizontal)
{
DrawWallLine(template, template.width / 2 - 1, 5, template.width / 2 - 1, 7);
DrawWallLine(template, template.width / 2 + 1, template.height - 8, template.width / 2 + 1, template.height - 6);
}
else
{
DrawWallLine(template, 5, template.height / 2 - 1, 7, template.height / 2 - 1);
DrawWallLine(template, template.width - 8, template.height / 2 + 1, template.width - 6, template.height / 2 + 1);
}
}
private static void DrawWallLine(ChunkTemplate template, int x0, int y0, int x1, int y1)
{
int dx = Math.Sign(x1 - x0);
int dy = Math.Sign(y1 - y0);
int x = x0;
int y = y0;
while (true)
{
template.SetWall(x, y, true);
if (x == x1 && y == y1)
{
break;
}
if (x != x1)
{
x += dx;
}
if (y != y1)
{
y += dy;
}
}
}
private static void ScatterEnvironment(ChunkTemplate template, int spacing)
{
for (int y = 2; y < template.height - 2; y += spacing + 2)
{
for (int x = 2; x < template.width - 2; x += spacing + 3)
{
if (!template.GetWall(x, y))
{
template.SetEnvironment(x, y, ((x + y) & 1) == 0);
}
}
}
}
}
}
@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: e5b485257bdc222448c314a7cd173845
@@ -1,10 +0,0 @@
using UnityEngine;
namespace InfiniteWorld
{
[DisallowMultipleComponent]
public sealed class WorldAutotileAuthoringRoot : MonoBehaviour
{
public WorldAutotileProfile profile;
}
}
@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: bbd0bbbd74f81084bb8661f761798856
@@ -1,18 +0,0 @@
using UnityEngine;
namespace InfiniteWorld
{
public enum WorldAutotileAuthoringSectionType
{
WallShapes,
BackgroundSample,
EnvironmentPalette
}
[DisallowMultipleComponent]
public sealed class WorldAutotileAuthoringSection : MonoBehaviour
{
public WorldAutotileAuthoringSectionType sectionType;
public Vector2Int size = Vector2Int.one;
}
}
@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: fbfe986c83f0c96419c804643d142e37
@@ -1,161 +0,0 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Tilemaps;
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)
{
return wallTiles != null ? wallTiles.GetTile(shape) : null;
}
public bool HasAnyAssignedTiles()
{
if (baseGroundTile != null)
{
return true;
}
if (wallTiles != null && wallTiles.HasAnyAssignedTiles())
{
return true;
}
if (environmentTiles != null)
{
for (int i = 0; i < environmentTiles.Count; i++)
{
if (environmentTiles[i] != null && environmentTiles[i].tile != null)
{
return true;
}
}
}
return false;
}
}
public enum AutoTileShape
{
Center,
Top,
Right,
Bottom,
Left,
OuterTopLeft,
OuterTopRight,
OuterBottomRight,
OuterBottomLeft,
InnerTopLeft,
InnerTopRight,
InnerBottomRight,
InnerBottomLeft
}
[Serializable]
public class AutoTileDefinition
{
public TileBase center;
public TileBase top;
public TileBase right;
public TileBase bottom;
public TileBase left;
public TileBase outerTopLeft;
public TileBase outerTopRight;
public TileBase outerBottomRight;
public TileBase outerBottomLeft;
public TileBase innerTopLeft;
public TileBase innerTopRight;
public TileBase innerBottomRight;
public TileBase innerBottomLeft;
public TileBase GetTile(AutoTileShape shape)
{
TileBase tile = 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,
_ => center
};
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 ||
top != null ||
right != null ||
bottom != null ||
left != null ||
outerTopLeft != null ||
outerTopRight != null ||
outerBottomRight != null ||
outerBottomLeft != null ||
innerTopLeft != null ||
innerTopRight != null ||
innerBottomRight != null ||
innerBottomLeft != null;
}
}
[Serializable]
public class EnvironmentTileEntry
{
public string id = "Environment";
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;
}
}
@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: e36b055e81273a54c8e736e2ef74fa50