Files
2026-06-22 16:18:34 +02:00

120 lines
5.6 KiB
C#

using System.Collections.Generic;
using UnityEngine;
namespace Game.WorldGen
{
public enum IslandShape { Radial, Square }
/// <summary>
/// All parameters for one world. Stored as an asset so you can version several
/// presets (test island, final island, flat playground, ...) side by side.
/// </summary>
[CreateAssetMenu(fileName = "WorldGenSettings", menuName = "Game/World Gen Settings")]
public class WorldGenSettings : ScriptableObject
{
[Header("Output")]
[Tooltip("Name of the root GameObject that will hold all terrain tiles.")]
public string worldRootName = "GeneratedWorld";
[Tooltip("Project folder where TerrainData + shared material assets are saved.")]
public string outputFolder = "Assets/GAME/World/Generated";
[Header("World layout")]
public int seed = 12345;
[Tooltip("World edge length in meters (square). 6000 = 6 km like ARK: The Island.")]
public float worldSizeMeters = 6000f;
[Tooltip("Tiles per side. 6 -> 36 tiles of 1 km.")]
[Range(1, 16)] public int tilesPerSide = 6;
[Tooltip("Heightmap resolution per tile. Must be 2^n + 1 (513 / 1025 / 2049 / 4097).")]
public int heightmapResolution = 1025;
[Tooltip("Max terrain height in meters (the terrain's Y size).")]
public float maxHeightMeters = 1000f;
[Tooltip("Reference sea level as a height fraction. Used for the water plane + splat rules.")]
[Range(0f, 1f)] public float seaLevel01 = 0.30f;
[Header("Base terrain noise")]
[Tooltip("Approx size of the largest land features, in meters.")]
public float baseFeatureSizeMeters = 1500f;
[Range(1, 10)] public int baseOctaves = 6;
[Range(1.5f, 3f)] public float lacunarity = 2f;
[Range(0.2f, 0.8f)] public float gain = 0.5f;
[Tooltip("Remaps the raw [0,1] base height -> lets you flatten lowlands or sharpen peaks.")]
public AnimationCurve heightCurve = AnimationCurve.Linear(0, 0, 1, 1);
[Header("Domain warp (organic, less grid-like coastlines)")]
public bool domainWarp = true;
[Range(0f, 1f)] public float warpStrength = 0.35f;
[Header("Procedural mountains (ridged)")]
[Range(0f, 1f), Tooltip("How strongly ridged mountains rise above the base.")]
public float mountainAmplitude = 0.55f;
public float mountainFeatureSizeMeters = 2200f;
[Range(1, 8)] public int mountainOctaves = 5;
[Tooltip("Size of the regions where mountains are allowed to appear.")]
public float mountainMaskFeatureSizeMeters = 3500f;
[Range(0f, 1f), Tooltip("Higher = mountains confined to fewer, tighter ranges.")]
public float mountainThreshold = 0.55f;
[Header("Island falloff")]
public bool island = true;
public IslandShape islandShape = IslandShape.Radial;
[Tooltip("Maps normalised distance-from-center (0..1) to a height multiplier (1 center -> 0 edge).")]
public AnimationCurve falloffCurve = new AnimationCurve(
new Keyframe(0f, 1f), new Keyframe(0.55f, 1f), new Keyframe(1f, 0f));
[Header("Hand-placed features")]
public List<TerrainFeature> features = new List<TerrainFeature>();
[Header("Auto texturing by height (optional)")]
[Tooltip("Enable height/slope based auto texturing at bake (and via the Apply button).")]
public bool autoTexture = false;
[Tooltip("Alphamap (splatmap) resolution per tile. Higher = smoother blends but more VRAM.")]
public int alphamapResolution = 1024;
[Tooltip("Height/slope bands, painted bottom-to-top. Heights are in world meters.")]
public List<SplatRule> splatRules = new List<SplatRule>();
[Header("Detail / grass painting")]
[Tooltip("Grid resolution for painted details (grass meshes). 0 = details disabled. " +
"1024 over a 1 km tile ≈ 1 detail cell/m. Must be a multiple of 'per patch'.")]
public int detailResolution = 1024;
[Tooltip("Detail cells per patch. detailResolution / this = number of patches per axis.")]
[Range(8, 128)] public int detailResolutionPerPatch = 32;
[Header("Terrain quality")]
[Range(1f, 50f)] public float heightmapPixelError = 5f;
public float basemapDistance = 1000f;
public bool drawInstanced = true;
void Reset()
{
heightCurve = AnimationCurve.Linear(0, 0, 1, 1);
falloffCurve = new AnimationCurve(
new Keyframe(0f, 1f), new Keyframe(0.55f, 1f), new Keyframe(1f, 0f));
}
/// <summary>Validates / repairs values; returns false with a reason if unusable.</summary>
public bool Validate(out string error)
{
error = null;
if (tilesPerSide < 1) { error = "tilesPerSide must be >= 1."; return false; }
if (worldSizeMeters <= 0f) { error = "worldSizeMeters must be > 0."; return false; }
if (!IsPowerOfTwoPlusOne(heightmapResolution))
{
error = $"heightmapResolution ({heightmapResolution}) must be 2^n + 1 (e.g. 513, 1025, 2049, 4097).";
return false;
}
if (string.IsNullOrWhiteSpace(outputFolder) || !outputFolder.StartsWith("Assets"))
{
error = "outputFolder must be a project path starting with 'Assets'.";
return false;
}
return true;
}
public static bool IsPowerOfTwoPlusOne(int v)
{
int n = v - 1;
return n >= 32 && (n & (n - 1)) == 0;
}
}
}