using UnityEditor; using UnityEngine; namespace InfiniteWorld.Editor { public class WorldGeneratorEditorWindow : EditorWindow { private enum BrushMode { Erase, Wall, Environment } private WorldAutotileProfile profile; private ChunkTemplate template; private SerializedObject serializedProfile; private Vector2 profileScroll; private Vector2 chunkScroll; private BrushMode brushMode = BrushMode.Wall; private bool isPainting; private int pendingWidth = 16; private int pendingHeight = 16; [MenuItem("Tools/Infinite World/World Builder")] public static void Open() { GetWindow("World Builder"); } private void OnGUI() { DrawToolbar(); EditorGUILayout.Space(6f); EditorGUILayout.BeginHorizontal(); DrawChunkEditor(); GUILayout.Space(8f); DrawProfileEditor(); EditorGUILayout.EndHorizontal(); } private void DrawToolbar() { EditorGUILayout.BeginHorizontal(EditorStyles.toolbar); template = (ChunkTemplate)EditorGUILayout.ObjectField(template, typeof(ChunkTemplate), false, GUILayout.Width(position.width * 0.38f)); profile = (WorldAutotileProfile)EditorGUILayout.ObjectField(profile, typeof(WorldAutotileProfile), false, GUILayout.Width(position.width * 0.38f)); if (GUILayout.Button("New Chunk", EditorStyles.toolbarButton, GUILayout.Width(80f))) { CreateChunkAsset(); } if (GUILayout.Button("New Profile", EditorStyles.toolbarButton, GUILayout.Width(80f))) { CreateProfileAsset(); } EditorGUILayout.EndHorizontal(); } private void DrawChunkEditor() { EditorGUILayout.BeginVertical(GUILayout.Width(position.width * 0.52f)); EditorGUILayout.LabelField("Chunk Painter", EditorStyles.boldLabel); if (template == null) { EditorGUILayout.HelpBox("Create or assign a ChunkTemplate asset. Paint walls and environment directly on the chunk grid and mark exits on each side.", MessageType.Info); EditorGUILayout.EndVertical(); return; } template.EnsureCellData(); pendingWidth = EditorGUILayout.IntField("Width", pendingWidth == 0 ? template.width : pendingWidth); pendingHeight = EditorGUILayout.IntField("Height", pendingHeight == 0 ? template.height : pendingHeight); EditorGUILayout.BeginHorizontal(); if (GUILayout.Button("Resize")) { Undo.RecordObject(template, "Resize Chunk Template"); template.Resize(pendingWidth, pendingHeight); EditorUtility.SetDirty(template); } if (GUILayout.Button("Clear")) { Undo.RecordObject(template, "Clear Chunk Template"); template.Clear(); EditorUtility.SetDirty(template); } if (GUILayout.Button("Border From Exits")) { Undo.RecordObject(template, "Apply Border Walls"); template.ApplyBorderWallsFromExits(3); EditorUtility.SetDirty(template); } EditorGUILayout.EndHorizontal(); EditorGUILayout.Space(4f); EditorGUILayout.LabelField("Brush", EditorStyles.miniBoldLabel); brushMode = (BrushMode)GUILayout.Toolbar((int)brushMode, new[] { "Erase", "Wall", "Environment" }); EditorGUILayout.Space(4f); EditorGUILayout.LabelField("Exits", EditorStyles.miniBoldLabel); EditorGUILayout.BeginHorizontal(); DrawExitToggle("Top", ChunkExit.Top); DrawExitToggle("Right", ChunkExit.Right); DrawExitToggle("Bottom", ChunkExit.Bottom); DrawExitToggle("Left", ChunkExit.Left); EditorGUILayout.EndHorizontal(); EditorGUILayout.Space(8f); chunkScroll = EditorGUILayout.BeginScrollView(chunkScroll, GUILayout.ExpandHeight(true)); Rect gridRect = GUILayoutUtility.GetRect(template.width * 24f, template.height * 24f, GUILayout.ExpandWidth(false), GUILayout.ExpandHeight(false)); DrawGrid(gridRect); EditorGUILayout.EndScrollView(); EditorGUILayout.HelpBox("Gray = floor, brown = wall, green = environment. Drag with the selected brush to paint the chunk visually.", MessageType.None); EditorGUILayout.EndVertical(); } private void DrawProfileEditor() { EditorGUILayout.BeginVertical(GUILayout.ExpandWidth(true)); EditorGUILayout.LabelField("Tile Profile", EditorStyles.boldLabel); if (profile == null) { EditorGUILayout.HelpBox("Assign a WorldAutotileProfile to map real tiles onto ground, walls, and environment.", MessageType.Info); EditorGUILayout.EndVertical(); return; } if (serializedProfile == null || serializedProfile.targetObject != profile) { serializedProfile = new SerializedObject(profile); } serializedProfile.Update(); profileScroll = EditorGUILayout.BeginScrollView(profileScroll); EditorGUILayout.PropertyField(serializedProfile.FindProperty("baseGroundTile")); EditorGUILayout.Space(6f); EditorGUILayout.LabelField("Wall Autotile", EditorStyles.miniBoldLabel); SerializedProperty walls = serializedProfile.FindProperty("wallTiles"); DrawWallGrid(walls); EditorGUILayout.Space(6f); EditorGUILayout.PropertyField(serializedProfile.FindProperty("environmentTiles"), true); EditorGUILayout.EndScrollView(); serializedProfile.ApplyModifiedProperties(); if (GUI.changed) { EditorUtility.SetDirty(profile); } EditorGUILayout.EndVertical(); } private void DrawWallGrid(SerializedProperty walls) { DrawTriple(walls, "outerTopLeft", "top", "outerTopRight"); DrawTriple(walls, "left", "center", "right"); DrawTriple(walls, "outerBottomLeft", "bottom", "outerBottomRight"); DrawDouble(walls, "innerTopLeft", "innerTopRight"); DrawDouble(walls, "innerBottomLeft", "innerBottomRight"); } private void DrawTriple(SerializedProperty root, string a, string b, string c) { EditorGUILayout.BeginHorizontal(); EditorGUILayout.PropertyField(root.FindPropertyRelative(a), GUIContent.none); EditorGUILayout.PropertyField(root.FindPropertyRelative(b), GUIContent.none); EditorGUILayout.PropertyField(root.FindPropertyRelative(c), GUIContent.none); EditorGUILayout.EndHorizontal(); } private void DrawDouble(SerializedProperty root, string a, string b) { EditorGUILayout.BeginHorizontal(); EditorGUILayout.PropertyField(root.FindPropertyRelative(a), GUIContent.none); EditorGUILayout.PropertyField(root.FindPropertyRelative(b), GUIContent.none); EditorGUILayout.EndHorizontal(); } private void DrawExitToggle(string label, ChunkExit exit) { bool current = template.GetExit(exit); bool next = GUILayout.Toggle(current, label, "Button"); if (next == current) { return; } Undo.RecordObject(template, "Toggle Chunk Exit"); switch (exit) { case ChunkExit.Top: template.exitTop = next; break; case ChunkExit.Right: template.exitRight = next; break; case ChunkExit.Bottom: template.exitBottom = next; break; case ChunkExit.Left: template.exitLeft = next; break; } EditorUtility.SetDirty(template); } private void DrawGrid(Rect rect) { const float cellSize = 24f; Event evt = Event.current; for (int y = 0; y < template.height; y++) { for (int x = 0; x < template.width; x++) { Rect cellRect = new Rect(rect.x + x * cellSize, rect.y + (template.height - 1 - y) * cellSize, cellSize - 1f, cellSize - 1f); EditorGUI.DrawRect(cellRect, GetCellColor(x, y)); if ((evt.type == EventType.MouseDown || evt.type == EventType.MouseDrag) && cellRect.Contains(evt.mousePosition) && evt.button == 0) { isPainting = true; PaintCell(x, y); evt.Use(); } } } if (evt.type == EventType.MouseUp) { isPainting = false; } if (isPainting) { Repaint(); } } private Color GetCellColor(int x, int y) { if (template.GetWall(x, y)) { return new Color(0.52f, 0.36f, 0.22f, 1f); } if (template.GetEnvironment(x, y)) { return new Color(0.2f, 0.54f, 0.25f, 1f); } return new Color(0.4f, 0.4f, 0.4f, 1f); } private void PaintCell(int x, int y) { Undo.RecordObject(template, "Paint Chunk Template"); switch (brushMode) { case BrushMode.Erase: template.SetWall(x, y, false); template.SetEnvironment(x, y, false); break; case BrushMode.Wall: template.SetWall(x, y, true); break; case BrushMode.Environment: template.SetEnvironment(x, y, true); break; } EditorUtility.SetDirty(template); } private void CreateChunkAsset() { string path = EditorUtility.SaveFilePanelInProject("Create Chunk Template", "ChunkTemplate", "asset", "Choose where to save the chunk template."); if (string.IsNullOrEmpty(path)) { return; } ChunkTemplate asset = CreateInstance(); asset.Resize(16, 16); asset.ApplyBorderWallsFromExits(3); AssetDatabase.CreateAsset(asset, path); AssetDatabase.SaveAssets(); template = asset; pendingWidth = asset.width; pendingHeight = asset.height; Selection.activeObject = asset; } private void CreateProfileAsset() { string path = EditorUtility.SaveFilePanelInProject("Create World Profile", "WorldAutotileProfile", "asset", "Choose where to save the tile profile."); if (string.IsNullOrEmpty(path)) { return; } WorldAutotileProfile asset = CreateInstance(); AssetDatabase.CreateAsset(asset, path); AssetDatabase.SaveAssets(); profile = asset; serializedProfile = new SerializedObject(profile); Selection.activeObject = asset; } } }