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

195 lines
8.3 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using UnityEditor;
using UnityEngine;
namespace Game.WorldGen
{
public class WorldGenWindow : EditorWindow
{
const string LastSettingsKey = "WorldGen.LastSettingsPath";
WorldGenSettings _settings;
Editor _settingsEditor;
Vector2 _scroll;
[MenuItem("Tools/World Generator")]
public static void Open()
{
var w = GetWindow<WorldGenWindow>("World Generator");
w.minSize = new Vector2(360f, 480f);
w.Show();
}
void OnEnable()
{
string path = EditorPrefs.GetString(LastSettingsKey, "");
if (!string.IsNullOrEmpty(path))
_settings = AssetDatabase.LoadAssetAtPath<WorldGenSettings>(path);
}
void OnDisable()
{
if (_settingsEditor != null) DestroyImmediate(_settingsEditor);
}
void OnGUI()
{
EditorGUILayout.Space(4);
EditorGUILayout.LabelField("World Generator", EditorStyles.boldLabel);
EditorGUILayout.HelpBox(
"Bakes a grid of stitched Unity Terrain tiles from a settings asset. " +
"Noise is sampled in world space, so tiles are seamless. " +
"Re-baking clears the previous world first.",
MessageType.Info);
EditorGUI.BeginChangeCheck();
_settings = (WorldGenSettings)EditorGUILayout.ObjectField(
"Settings", _settings, typeof(WorldGenSettings), false);
if (EditorGUI.EndChangeCheck())
{
EditorPrefs.SetString(LastSettingsKey,
_settings != null ? AssetDatabase.GetAssetPath(_settings) : "");
if (_settingsEditor != null) { DestroyImmediate(_settingsEditor); _settingsEditor = null; }
}
if (_settings == null)
{
EditorGUILayout.Space(6);
if (GUILayout.Button("Create new settings asset…", GUILayout.Height(28)))
CreateSettingsAsset();
EditorGUILayout.HelpBox("Assign or create a WorldGenSettings asset to begin.", MessageType.Warning);
return;
}
DrawSummary();
EditorGUILayout.Space(6);
using (var sv = new EditorGUILayout.ScrollViewScope(_scroll))
{
_scroll = sv.scrollPosition;
if (_settingsEditor == null) _settingsEditor = Editor.CreateEditor(_settings);
_settingsEditor.OnInspectorGUI();
}
EditorGUILayout.Space(8);
DrawActions();
}
void DrawSummary()
{
float tileSize = _settings.worldSizeMeters / Mathf.Max(1, _settings.tilesPerSide);
int n = _settings.tilesPerSide;
float metersPerTexel = tileSize / Mathf.Max(1, _settings.heightmapResolution - 1);
EditorGUILayout.Space(4);
EditorGUILayout.LabelField(
$"{n}×{n} = {n * n} tiles · {tileSize:0} m/tile · {metersPerTexel:0.00} m/texel",
EditorStyles.miniBoldLabel);
if (!WorldGenSettings.IsPowerOfTwoPlusOne(_settings.heightmapResolution))
EditorGUILayout.HelpBox(
"Heightmap resolution must be 2^n + 1 (513, 1025, 2049, 4097).",
MessageType.Error);
}
void DrawActions()
{
using (new EditorGUILayout.HorizontalScope())
{
GUI.backgroundColor = new Color(0.6f, 0.9f, 0.6f);
if (GUILayout.Button("Generate World", GUILayout.Height(34)))
{
if (EditorUtility.DisplayDialog(
"Generate World",
$"This clears any existing '{_settings.worldRootName}' and regenerates " +
$"{_settings.tilesPerSide * _settings.tilesPerSide} terrain tiles. Continue?",
"Generate", "Cancel"))
{
WorldGenerator.Generate(_settings);
}
}
GUI.backgroundColor = Color.white;
if (GUILayout.Button("Clear", GUILayout.Height(34), GUILayout.Width(80)))
{
if (EditorUtility.DisplayDialog("Clear World",
$"Delete '{_settings.worldRootName}' and its generated TerrainData assets?",
"Clear", "Cancel"))
{
WorldGenerator.ClearWorld(_settings);
}
}
}
EditorGUILayout.Space(4);
if (GUILayout.Button("Sync Layers to All Tiles", GUILayout.Height(24)))
WorldGenerator.SyncLayers(_settings);
EditorGUILayout.HelpBox(
"Set up your textures on ONE tile (Terrain ▸ Paint ▸ Edit Terrain Layers), select it, " +
"then click this to copy the same palette onto every tile so you can paint everywhere.",
MessageType.None);
EditorGUILayout.Space(2);
float aTexel = (_settings.worldSizeMeters / Mathf.Max(1, _settings.tilesPerSide))
/ Mathf.Max(16, _settings.alphamapResolution);
if (GUILayout.Button($"Apply Splatmap Resolution to All Tiles ({aTexel:0.00} m/texel)", GUILayout.Height(24)))
WorldGenerator.ApplyAlphamapResolution(_settings);
EditorGUILayout.HelpBox(
"Blocky paint? Raise 'Alphamap Resolution' above, then click this. " +
"1024 ≈ 1 m/texel (good balance). 2048 is sharper but uses a lot more VRAM across 36 tiles.",
MessageType.None);
EditorGUILayout.Space(2);
if (GUILayout.Button("Apply Detail Resolution to All Tiles (grass painting)", GUILayout.Height(24)))
WorldGenerator.ApplyDetailResolution(_settings);
EditorGUILayout.HelpBox(
"Can't paint grass/details (\"Detail patches allocated: 0\")? Click this. " +
"It initialises the detail grid on every tile so the Paint Details tool works.",
MessageType.None);
EditorGUILayout.Space(6);
EditorGUILayout.LabelField("Auto texturing by height", EditorStyles.boldLabel);
float sea = _settings.seaLevel01 * _settings.maxHeightMeters;
EditorGUILayout.LabelField($"Sea level ≈ {sea:0} m · Max height = {_settings.maxHeightMeters:0} m",
EditorStyles.miniLabel);
using (new EditorGUILayout.HorizontalScope())
{
if (GUILayout.Button("Add Height-Band Preset", GUILayout.Height(24)))
WorldGenerator.AddHeightBandPreset(_settings);
using (new EditorGUI.DisabledScope(!_settings.autoTexture))
{
if (GUILayout.Button("Apply Auto-Texture to All Tiles", GUILayout.Height(24)))
{
if (EditorUtility.DisplayDialog("Auto Texture",
"This repaints the splatmap on every tile from the height bands and " +
"overwrites any manual painting. Continue?", "Apply", "Cancel"))
{
WorldGenerator.ApplyAutoTexture(_settings);
}
}
}
}
EditorGUILayout.HelpBox(
"1) Add the preset (Sand/Grass/Rock/Snow). 2) Drop a Terrain Layer into each rule and " +
"tune the meter ranges + fades. 3) Apply. 'Auto texture' must be checked.",
MessageType.None);
}
void CreateSettingsAsset()
{
string path = EditorUtility.SaveFilePanelInProject(
"Create World Gen Settings", "WorldGenSettings", "asset",
"Choose where to save the settings asset.", "Assets");
if (string.IsNullOrEmpty(path)) return;
var asset = ScriptableObject.CreateInstance<WorldGenSettings>();
AssetDatabase.CreateAsset(asset, path);
AssetDatabase.SaveAssets();
_settings = asset;
EditorPrefs.SetString(LastSettingsKey, path);
Selection.activeObject = asset;
}
}
}