195 lines
8.3 KiB
C#
195 lines
8.3 KiB
C#
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;
|
||
}
|
||
}
|
||
}
|