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("World Generator"); w.minSize = new Vector2(360f, 480f); w.Show(); } void OnEnable() { string path = EditorPrefs.GetString(LastSettingsKey, ""); if (!string.IsNullOrEmpty(path)) _settings = AssetDatabase.LoadAssetAtPath(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(); AssetDatabase.CreateAsset(asset, path); AssetDatabase.SaveAssets(); _settings = asset; EditorPrefs.SetString(LastSettingsKey, path); Selection.activeObject = asset; } } }