[Add] WorldAutotile authoring pipeline
This commit is contained in:
@@ -0,0 +1,75 @@
|
|||||||
|
namespace InfiniteWorld.Editor
|
||||||
|
{
|
||||||
|
internal static class WorldAutotileEditorLabels
|
||||||
|
{
|
||||||
|
public static string GetShapeLabel(AutoTileShape shape)
|
||||||
|
{
|
||||||
|
switch (shape)
|
||||||
|
{
|
||||||
|
case AutoTileShape.Center:
|
||||||
|
return "Center";
|
||||||
|
case AutoTileShape.Top:
|
||||||
|
return "Top";
|
||||||
|
case AutoTileShape.Right:
|
||||||
|
return "Right";
|
||||||
|
case AutoTileShape.Bottom:
|
||||||
|
return "Bottom";
|
||||||
|
case AutoTileShape.Left:
|
||||||
|
return "Left";
|
||||||
|
case AutoTileShape.OuterTopLeft:
|
||||||
|
return "Outer Top Left";
|
||||||
|
case AutoTileShape.OuterTopRight:
|
||||||
|
return "Outer Top Right";
|
||||||
|
case AutoTileShape.OuterBottomRight:
|
||||||
|
return "Outer Bottom Right";
|
||||||
|
case AutoTileShape.OuterBottomLeft:
|
||||||
|
return "Outer Bottom Left";
|
||||||
|
case AutoTileShape.InnerTopLeft:
|
||||||
|
return "Inner Top Left";
|
||||||
|
case AutoTileShape.InnerTopRight:
|
||||||
|
return "Inner Top Right";
|
||||||
|
case AutoTileShape.InnerBottomRight:
|
||||||
|
return "Inner Bottom Right";
|
||||||
|
case AutoTileShape.InnerBottomLeft:
|
||||||
|
return "Inner Bottom Left";
|
||||||
|
default:
|
||||||
|
return shape.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetPropertyName(AutoTileShape shape)
|
||||||
|
{
|
||||||
|
switch (shape)
|
||||||
|
{
|
||||||
|
case AutoTileShape.Center:
|
||||||
|
return "center";
|
||||||
|
case AutoTileShape.Top:
|
||||||
|
return "top";
|
||||||
|
case AutoTileShape.Right:
|
||||||
|
return "right";
|
||||||
|
case AutoTileShape.Bottom:
|
||||||
|
return "bottom";
|
||||||
|
case AutoTileShape.Left:
|
||||||
|
return "left";
|
||||||
|
case AutoTileShape.OuterTopLeft:
|
||||||
|
return "outerTopLeft";
|
||||||
|
case AutoTileShape.OuterTopRight:
|
||||||
|
return "outerTopRight";
|
||||||
|
case AutoTileShape.OuterBottomRight:
|
||||||
|
return "outerBottomRight";
|
||||||
|
case AutoTileShape.OuterBottomLeft:
|
||||||
|
return "outerBottomLeft";
|
||||||
|
case AutoTileShape.InnerTopLeft:
|
||||||
|
return "innerTopLeft";
|
||||||
|
case AutoTileShape.InnerTopRight:
|
||||||
|
return "innerTopRight";
|
||||||
|
case AutoTileShape.InnerBottomRight:
|
||||||
|
return "innerBottomRight";
|
||||||
|
case AutoTileShape.InnerBottomLeft:
|
||||||
|
return "innerBottomLeft";
|
||||||
|
default:
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 7f9b9a3347cfc82428971840cb651f99
|
||||||
@@ -0,0 +1,642 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Reflection;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.Tilemaps;
|
||||||
|
|
||||||
|
namespace InfiniteWorld.Editor
|
||||||
|
{
|
||||||
|
[InitializeOnLoad]
|
||||||
|
public static class WorldAutotileProfilePipeline
|
||||||
|
{
|
||||||
|
private const string AuthoringLayoutFolder = "Assets/Editor/Generated/WorldAutotileAuthoring";
|
||||||
|
|
||||||
|
private static readonly AutoTileShape[] WallShapeOrder =
|
||||||
|
{
|
||||||
|
AutoTileShape.OuterTopLeft,
|
||||||
|
AutoTileShape.Top,
|
||||||
|
AutoTileShape.OuterTopRight,
|
||||||
|
AutoTileShape.Left,
|
||||||
|
AutoTileShape.Center,
|
||||||
|
AutoTileShape.Right,
|
||||||
|
AutoTileShape.OuterBottomLeft,
|
||||||
|
AutoTileShape.Bottom,
|
||||||
|
AutoTileShape.OuterBottomRight,
|
||||||
|
AutoTileShape.InnerTopLeft,
|
||||||
|
AutoTileShape.InnerTopRight,
|
||||||
|
AutoTileShape.InnerBottomLeft,
|
||||||
|
AutoTileShape.InnerBottomRight
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly Vector3Int[] WallShapePositions =
|
||||||
|
{
|
||||||
|
new Vector3Int(0, 4, 0),
|
||||||
|
new Vector3Int(1, 4, 0),
|
||||||
|
new Vector3Int(2, 4, 0),
|
||||||
|
new Vector3Int(0, 3, 0),
|
||||||
|
new Vector3Int(1, 3, 0),
|
||||||
|
new Vector3Int(2, 3, 0),
|
||||||
|
new Vector3Int(0, 2, 0),
|
||||||
|
new Vector3Int(1, 2, 0),
|
||||||
|
new Vector3Int(2, 2, 0),
|
||||||
|
new Vector3Int(0, 1, 0),
|
||||||
|
new Vector3Int(2, 1, 0),
|
||||||
|
new Vector3Int(0, 0, 0),
|
||||||
|
new Vector3Int(2, 0, 0)
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly HashSet<string> PendingProfilePaths = new HashSet<string>();
|
||||||
|
private static readonly MethodInfo SetIconForObjectMethod = typeof(EditorGUIUtility).GetMethod("SetIconForObject", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
|
||||||
|
private static bool refreshQueued;
|
||||||
|
|
||||||
|
static WorldAutotileProfilePipeline()
|
||||||
|
{
|
||||||
|
AssemblyReloadEvents.beforeAssemblyReload += ClearQueue;
|
||||||
|
EditorApplication.projectChanged += QueueAllProfilesRefresh;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void QueueProfileRefresh(WorldAutotileProfile profile)
|
||||||
|
{
|
||||||
|
if (profile == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string path = AssetDatabase.GetAssetPath(profile);
|
||||||
|
if (string.IsNullOrEmpty(path))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PendingProfilePaths.Add(path);
|
||||||
|
if (refreshQueued)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshQueued = true;
|
||||||
|
EditorApplication.delayCall += ProcessPendingProfiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetAuthoringLayoutPath(WorldAutotileProfile profile)
|
||||||
|
{
|
||||||
|
return AuthoringLayoutFolder + "/" + profile.name + "_AuthoringLayout.prefab";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GameObject LoadAuthoringLayoutAsset(WorldAutotileProfile profile)
|
||||||
|
{
|
||||||
|
if (profile == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return AssetDatabase.LoadAssetAtPath<GameObject>(GetAuthoringLayoutPath(profile));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void GenerateAuthoringLayout(WorldAutotileProfile profile, bool pingAsset = true)
|
||||||
|
{
|
||||||
|
if (profile == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
EnsureFolderExists(AuthoringLayoutFolder);
|
||||||
|
string path = GetAuthoringLayoutPath(profile);
|
||||||
|
DeleteExistingAuthoringLayout(path);
|
||||||
|
|
||||||
|
GameObject root = BuildAuthoringLayoutRoot(profile);
|
||||||
|
PrefabUtility.SaveAsPrefabAsset(root, path);
|
||||||
|
Object.DestroyImmediate(root);
|
||||||
|
|
||||||
|
AssetDatabase.SaveAssets();
|
||||||
|
AssetDatabase.Refresh();
|
||||||
|
|
||||||
|
if (!pingAsset)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GameObject layout = AssetDatabase.LoadAssetAtPath<GameObject>(path);
|
||||||
|
if (layout != null)
|
||||||
|
{
|
||||||
|
EditorGUIUtility.PingObject(layout);
|
||||||
|
Selection.activeObject = layout;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool BuildProfileFromAuthoringLayout(WorldAutotileProfile profile, bool pingProfile = true)
|
||||||
|
{
|
||||||
|
if (profile == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
string path = GetAuthoringLayoutPath(profile);
|
||||||
|
if (!System.IO.File.Exists(path))
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"Authoring layout was not found for profile '{profile.name}' at '{path}'.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
GameObject prefabRoot = PrefabUtility.LoadPrefabContents(path);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Undo.RecordObject(profile, "Build World Autotile Profile From Layout");
|
||||||
|
if (!TryBuildProfileFromLayout(prefabRoot, profile, out string error))
|
||||||
|
{
|
||||||
|
Debug.LogError(error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorUtility.SetDirty(profile);
|
||||||
|
AssetDatabase.SaveAssets();
|
||||||
|
|
||||||
|
if (pingProfile)
|
||||||
|
{
|
||||||
|
EditorGUIUtility.PingObject(profile);
|
||||||
|
Selection.activeObject = profile;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (profile.autoRefreshGeneratedWorld && Application.isPlaying)
|
||||||
|
{
|
||||||
|
RefreshActiveGenerators(profile);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
PrefabUtility.UnloadPrefabContents(prefabRoot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ProcessPendingProfiles()
|
||||||
|
{
|
||||||
|
refreshQueued = false;
|
||||||
|
if (EditorApplication.isCompiling || EditorApplication.isUpdating)
|
||||||
|
{
|
||||||
|
if (PendingProfilePaths.Count > 0)
|
||||||
|
{
|
||||||
|
refreshQueued = true;
|
||||||
|
EditorApplication.delayCall += ProcessPendingProfiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string[] paths = new string[PendingProfilePaths.Count];
|
||||||
|
PendingProfilePaths.CopyTo(paths);
|
||||||
|
PendingProfilePaths.Clear();
|
||||||
|
|
||||||
|
for (int i = 0; i < paths.Length; i++)
|
||||||
|
{
|
||||||
|
WorldAutotileProfile profile = AssetDatabase.LoadAssetAtPath<WorldAutotileProfile>(paths[i]);
|
||||||
|
if (profile == null || !profile.autoUpdatePaletteLayout)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (LoadAuthoringLayoutAsset(profile) == null)
|
||||||
|
{
|
||||||
|
GenerateAuthoringLayout(profile, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GameObject BuildAuthoringLayoutRoot(WorldAutotileProfile profile)
|
||||||
|
{
|
||||||
|
GameObject root = new GameObject(profile.name + "_AuthoringLayout", typeof(Grid), typeof(WorldAutotileAuthoringRoot));
|
||||||
|
WorldAutotileAuthoringRoot rootMarker = root.GetComponent<WorldAutotileAuthoringRoot>();
|
||||||
|
rootMarker.profile = profile;
|
||||||
|
|
||||||
|
Transform guidesRoot = new GameObject("Guides").transform;
|
||||||
|
guidesRoot.SetParent(root.transform, false);
|
||||||
|
CreateGuideLabel(guidesRoot, "Workflow Label", new Vector3(5f, 6.6f, 0f), "sv_label_0");
|
||||||
|
|
||||||
|
Transform wallSection = CreateSection(root.transform, "WallShapes", WorldAutotileAuthoringSectionType.WallShapes, new Vector3(0f, 0f, 0f), new Vector2Int(3, 5));
|
||||||
|
Tilemap wallBackground = CreateTilemap(wallSection, "Background", 0);
|
||||||
|
Tilemap wallTiles = CreateTilemap(wallSection, "Walls", 1);
|
||||||
|
PopulateWallSection(profile, wallBackground, wallTiles);
|
||||||
|
CreateGuideLabel(guidesRoot, "Wall Shapes Label", wallSection.localPosition + new Vector3(1f, 5.6f, 0f), "sv_label_3");
|
||||||
|
CreateWallShapeLabels(guidesRoot, wallSection.localPosition, "sv_label_6");
|
||||||
|
|
||||||
|
Transform backgroundSection = CreateSection(root.transform, "BackgroundSample", WorldAutotileAuthoringSectionType.BackgroundSample, new Vector3(5f, 0f, 0f), new Vector2Int(5, 3));
|
||||||
|
Tilemap backgroundTilemap = CreateTilemap(backgroundSection, "Background", 0);
|
||||||
|
PopulateBackgroundSection(profile, backgroundTilemap);
|
||||||
|
CreateGuideLabel(guidesRoot, "Background Sample Label", backgroundSection.localPosition + new Vector3(2f, 3.6f, 0f), "sv_label_4");
|
||||||
|
|
||||||
|
int environmentWidth = Mathf.Max(1, CountAssignedEnvironmentTiles(profile));
|
||||||
|
Transform environmentSection = CreateSection(root.transform, "EnvironmentPalette", WorldAutotileAuthoringSectionType.EnvironmentPalette, new Vector3(0f, -3f, 0f), new Vector2Int(environmentWidth, 1));
|
||||||
|
Tilemap environmentBackground = CreateTilemap(environmentSection, "Background", 0);
|
||||||
|
Tilemap environmentTilemap = CreateTilemap(environmentSection, "Environment", 1);
|
||||||
|
PopulateEnvironmentSection(profile, environmentBackground, environmentTilemap);
|
||||||
|
float environmentCenterX = Mathf.Max(0f, (environmentWidth - 1) * 0.5f);
|
||||||
|
CreateGuideLabel(guidesRoot, "Environment Palette Label", environmentSection.localPosition + new Vector3(environmentCenterX, 1.6f, 0f), "sv_label_2");
|
||||||
|
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Transform CreateSection(Transform parent, string name, WorldAutotileAuthoringSectionType sectionType, Vector3 localPosition, Vector2Int size)
|
||||||
|
{
|
||||||
|
GameObject section = new GameObject(name, typeof(WorldAutotileAuthoringSection));
|
||||||
|
section.transform.SetParent(parent, false);
|
||||||
|
section.transform.localPosition = localPosition;
|
||||||
|
|
||||||
|
WorldAutotileAuthoringSection marker = section.GetComponent<WorldAutotileAuthoringSection>();
|
||||||
|
marker.sectionType = sectionType;
|
||||||
|
marker.size = size;
|
||||||
|
return section.transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void CreateGuideLabel(Transform parent, string labelName, Vector3 position, string iconName)
|
||||||
|
{
|
||||||
|
GameObject label = new GameObject(labelName);
|
||||||
|
label.transform.SetParent(parent, false);
|
||||||
|
label.transform.localPosition = position;
|
||||||
|
SetIconForObject(label, iconName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void CreateWallShapeLabels(Transform parent, Vector3 sectionPosition, string iconName)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < WallShapeOrder.Length; i++)
|
||||||
|
{
|
||||||
|
Vector3Int cell = WallShapePositions[i];
|
||||||
|
string label = WorldAutotileEditorLabels.GetShapeLabel(WallShapeOrder[i]) + " Label";
|
||||||
|
Vector3 position = sectionPosition + new Vector3(cell.x + 0.5f, cell.y + 0.5f, 0f);
|
||||||
|
CreateGuideLabel(parent, label, position, iconName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void PopulateWallSection(WorldAutotileProfile profile, Tilemap backgroundTilemap, Tilemap wallsTilemap)
|
||||||
|
{
|
||||||
|
TileBase background = profile.baseGroundTile;
|
||||||
|
for (int i = 0; i < WallShapeOrder.Length; i++)
|
||||||
|
{
|
||||||
|
Vector3Int position = WallShapePositions[i];
|
||||||
|
if (background != null)
|
||||||
|
{
|
||||||
|
backgroundTilemap.SetTile(position, background);
|
||||||
|
}
|
||||||
|
|
||||||
|
TileBase wallTile = profile.wallTiles != null ? profile.wallTiles.GetAssignedTile(WallShapeOrder[i]) : null;
|
||||||
|
if (wallTile != null)
|
||||||
|
{
|
||||||
|
wallsTilemap.SetTile(position, wallTile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (background != null)
|
||||||
|
{
|
||||||
|
backgroundTilemap.SetTile(new Vector3Int(1, 1, 0), background);
|
||||||
|
backgroundTilemap.SetTile(new Vector3Int(1, 0, 0), background);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void PopulateBackgroundSection(WorldAutotileProfile profile, Tilemap backgroundTilemap)
|
||||||
|
{
|
||||||
|
if (profile.baseGroundTile == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int x = 0; x < 5; x++)
|
||||||
|
{
|
||||||
|
for (int y = 0; y < 3; y++)
|
||||||
|
{
|
||||||
|
backgroundTilemap.SetTile(new Vector3Int(x, y, 0), profile.baseGroundTile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void PopulateEnvironmentSection(WorldAutotileProfile profile, Tilemap backgroundTilemap, Tilemap environmentTilemap)
|
||||||
|
{
|
||||||
|
if (profile.environmentTiles == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int x = 0;
|
||||||
|
for (int i = 0; i < profile.environmentTiles.Count; i++)
|
||||||
|
{
|
||||||
|
EnvironmentTileEntry entry = profile.environmentTiles[i];
|
||||||
|
if (entry == null || entry.tile == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3Int position = new Vector3Int(x, 0, 0);
|
||||||
|
if (profile.baseGroundTile != null)
|
||||||
|
{
|
||||||
|
backgroundTilemap.SetTile(position, profile.baseGroundTile);
|
||||||
|
}
|
||||||
|
|
||||||
|
environmentTilemap.SetTile(position, entry.tile);
|
||||||
|
x++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Tilemap CreateTilemap(Transform parent, string name, int sortingOrder)
|
||||||
|
{
|
||||||
|
GameObject child = new GameObject(name, typeof(Tilemap), typeof(TilemapRenderer));
|
||||||
|
child.transform.SetParent(parent, false);
|
||||||
|
TilemapRenderer renderer = child.GetComponent<TilemapRenderer>();
|
||||||
|
renderer.sortingOrder = sortingOrder;
|
||||||
|
return child.GetComponent<Tilemap>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryBuildProfileFromLayout(GameObject prefabRoot, WorldAutotileProfile profile, out string error)
|
||||||
|
{
|
||||||
|
error = null;
|
||||||
|
|
||||||
|
if (prefabRoot == null)
|
||||||
|
{
|
||||||
|
error = "Could not load authoring layout prefab contents.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
WorldAutotileAuthoringRoot rootMarker = prefabRoot.GetComponent<WorldAutotileAuthoringRoot>();
|
||||||
|
if (rootMarker == null)
|
||||||
|
{
|
||||||
|
error = $"Authoring layout '{prefabRoot.name}' is missing {nameof(WorldAutotileAuthoringRoot)}.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rootMarker.profile != null && rootMarker.profile != profile)
|
||||||
|
{
|
||||||
|
error = $"Authoring layout '{prefabRoot.name}' is linked to profile '{rootMarker.profile.name}', not '{profile.name}'.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
WorldAutotileAuthoringSection wallSection = FindSection(prefabRoot, WorldAutotileAuthoringSectionType.WallShapes);
|
||||||
|
WorldAutotileAuthoringSection backgroundSection = FindSection(prefabRoot, WorldAutotileAuthoringSectionType.BackgroundSample);
|
||||||
|
WorldAutotileAuthoringSection environmentSection = FindSection(prefabRoot, WorldAutotileAuthoringSectionType.EnvironmentPalette);
|
||||||
|
|
||||||
|
if (wallSection == null || backgroundSection == null || environmentSection == null)
|
||||||
|
{
|
||||||
|
error = $"Authoring layout '{prefabRoot.name}' is missing one or more required section markers.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Tilemap wallTilemap = FindTilemap(wallSection.transform, "Walls");
|
||||||
|
Tilemap backgroundTilemap = FindTilemap(backgroundSection.transform, "Background");
|
||||||
|
Tilemap environmentTilemap = FindTilemap(environmentSection.transform, "Environment");
|
||||||
|
|
||||||
|
if (wallTilemap == null || backgroundTilemap == null || environmentTilemap == null)
|
||||||
|
{
|
||||||
|
error = $"Authoring layout '{prefabRoot.name}' is missing one or more required tilemaps.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
profile.baseGroundTile = FindFirstTile(backgroundTilemap, backgroundSection.size);
|
||||||
|
profile.wallTiles = ExtractWallTiles(wallTilemap);
|
||||||
|
profile.environmentTiles = ExtractEnvironmentTiles(environmentTilemap, environmentSection.size, profile.environmentTiles);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static WorldAutotileAuthoringSection FindSection(GameObject root, WorldAutotileAuthoringSectionType sectionType)
|
||||||
|
{
|
||||||
|
WorldAutotileAuthoringSection[] sections = root.GetComponentsInChildren<WorldAutotileAuthoringSection>(true);
|
||||||
|
for (int i = 0; i < sections.Length; i++)
|
||||||
|
{
|
||||||
|
if (sections[i].sectionType == sectionType)
|
||||||
|
{
|
||||||
|
return sections[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Tilemap FindTilemap(Transform root, string name)
|
||||||
|
{
|
||||||
|
Transform child = root.Find(name);
|
||||||
|
return child != null ? child.GetComponent<Tilemap>() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TileBase FindFirstTile(Tilemap tilemap, Vector2Int size)
|
||||||
|
{
|
||||||
|
for (int y = 0; y < Mathf.Max(1, size.y); y++)
|
||||||
|
{
|
||||||
|
for (int x = 0; x < Mathf.Max(1, size.x); x++)
|
||||||
|
{
|
||||||
|
TileBase tile = tilemap.GetTile(new Vector3Int(x, y, 0));
|
||||||
|
if (tile != null)
|
||||||
|
{
|
||||||
|
return tile;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AutoTileDefinition ExtractWallTiles(Tilemap wallTilemap)
|
||||||
|
{
|
||||||
|
AutoTileDefinition definition = new AutoTileDefinition();
|
||||||
|
for (int i = 0; i < WallShapeOrder.Length; i++)
|
||||||
|
{
|
||||||
|
AssignWallTile(definition, WallShapeOrder[i], wallTilemap.GetTile(WallShapePositions[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return definition;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AssignWallTile(AutoTileDefinition definition, AutoTileShape shape, TileBase tile)
|
||||||
|
{
|
||||||
|
switch (shape)
|
||||||
|
{
|
||||||
|
case AutoTileShape.Center:
|
||||||
|
definition.center = tile;
|
||||||
|
break;
|
||||||
|
case AutoTileShape.Top:
|
||||||
|
definition.top = tile;
|
||||||
|
break;
|
||||||
|
case AutoTileShape.Right:
|
||||||
|
definition.right = tile;
|
||||||
|
break;
|
||||||
|
case AutoTileShape.Bottom:
|
||||||
|
definition.bottom = tile;
|
||||||
|
break;
|
||||||
|
case AutoTileShape.Left:
|
||||||
|
definition.left = tile;
|
||||||
|
break;
|
||||||
|
case AutoTileShape.OuterTopLeft:
|
||||||
|
definition.outerTopLeft = tile;
|
||||||
|
break;
|
||||||
|
case AutoTileShape.OuterTopRight:
|
||||||
|
definition.outerTopRight = tile;
|
||||||
|
break;
|
||||||
|
case AutoTileShape.OuterBottomRight:
|
||||||
|
definition.outerBottomRight = tile;
|
||||||
|
break;
|
||||||
|
case AutoTileShape.OuterBottomLeft:
|
||||||
|
definition.outerBottomLeft = tile;
|
||||||
|
break;
|
||||||
|
case AutoTileShape.InnerTopLeft:
|
||||||
|
definition.innerTopLeft = tile;
|
||||||
|
break;
|
||||||
|
case AutoTileShape.InnerTopRight:
|
||||||
|
definition.innerTopRight = tile;
|
||||||
|
break;
|
||||||
|
case AutoTileShape.InnerBottomRight:
|
||||||
|
definition.innerBottomRight = tile;
|
||||||
|
break;
|
||||||
|
case AutoTileShape.InnerBottomLeft:
|
||||||
|
definition.innerBottomLeft = tile;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<EnvironmentTileEntry> ExtractEnvironmentTiles(Tilemap tilemap, Vector2Int size, List<EnvironmentTileEntry> previousEntries)
|
||||||
|
{
|
||||||
|
List<EnvironmentTileEntry> entries = new List<EnvironmentTileEntry>();
|
||||||
|
for (int y = 0; y < Mathf.Max(1, size.y); y++)
|
||||||
|
{
|
||||||
|
for (int x = 0; x < Mathf.Max(1, size.x); x++)
|
||||||
|
{
|
||||||
|
TileBase tile = tilemap.GetTile(new Vector3Int(x, y, 0));
|
||||||
|
if (tile == null || ContainsEnvironmentTile(entries, tile))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
EnvironmentTileEntry existing = FindEnvironmentEntry(previousEntries, tile);
|
||||||
|
entries.Add(new EnvironmentTileEntry
|
||||||
|
{
|
||||||
|
id = existing != null && !string.IsNullOrWhiteSpace(existing.id) ? existing.id : tile.name,
|
||||||
|
tile = tile,
|
||||||
|
weight = existing != null ? existing.weight : 1f
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool ContainsEnvironmentTile(List<EnvironmentTileEntry> entries, TileBase tile)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < entries.Count; i++)
|
||||||
|
{
|
||||||
|
if (entries[i] != null && entries[i].tile == tile)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static EnvironmentTileEntry FindEnvironmentEntry(List<EnvironmentTileEntry> entries, TileBase tile)
|
||||||
|
{
|
||||||
|
if (entries == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < entries.Count; i++)
|
||||||
|
{
|
||||||
|
if (entries[i] != null && entries[i].tile == tile)
|
||||||
|
{
|
||||||
|
return entries[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int CountAssignedEnvironmentTiles(WorldAutotileProfile profile)
|
||||||
|
{
|
||||||
|
if (profile.environmentTiles == null)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
|
for (int i = 0; i < profile.environmentTiles.Count; i++)
|
||||||
|
{
|
||||||
|
if (profile.environmentTiles[i] != null && profile.environmentTiles[i].tile != null)
|
||||||
|
{
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DeleteExistingAuthoringLayout(string path)
|
||||||
|
{
|
||||||
|
if (!File.Exists(path))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AssetDatabase.DeleteAsset(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SetIconForObject(GameObject gameObject, string iconName)
|
||||||
|
{
|
||||||
|
if (gameObject == null || SetIconForObjectMethod == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Texture2D icon = EditorGUIUtility.IconContent(iconName).image as Texture2D;
|
||||||
|
if (icon == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetIconForObjectMethod.Invoke(null, new object[] { gameObject, icon });
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RefreshActiveGenerators(WorldAutotileProfile profile)
|
||||||
|
{
|
||||||
|
InfiniteWorldGenerator[] generators = Object.FindObjectsByType<InfiniteWorldGenerator>(FindObjectsSortMode.None);
|
||||||
|
for (int i = 0; i < generators.Length; i++)
|
||||||
|
{
|
||||||
|
InfiniteWorldGenerator generator = generators[i];
|
||||||
|
if (generator != null && generator.UsesProfile(profile))
|
||||||
|
{
|
||||||
|
generator.EditorRefreshFromProfile();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void EnsureFolderExists(string assetFolder)
|
||||||
|
{
|
||||||
|
string[] parts = assetFolder.Split('/');
|
||||||
|
string current = parts[0];
|
||||||
|
for (int i = 1; i < parts.Length; i++)
|
||||||
|
{
|
||||||
|
string next = current + "/" + parts[i];
|
||||||
|
if (!AssetDatabase.IsValidFolder(next))
|
||||||
|
{
|
||||||
|
AssetDatabase.CreateFolder(current, parts[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
current = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ClearQueue()
|
||||||
|
{
|
||||||
|
PendingProfilePaths.Clear();
|
||||||
|
refreshQueued = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void QueueAllProfilesRefresh()
|
||||||
|
{
|
||||||
|
string[] guids = AssetDatabase.FindAssets("t:WorldAutotileProfile");
|
||||||
|
for (int i = 0; i < guids.Length; i++)
|
||||||
|
{
|
||||||
|
string path = AssetDatabase.GUIDToAssetPath(guids[i]);
|
||||||
|
WorldAutotileProfile profile = AssetDatabase.LoadAssetAtPath<WorldAutotileProfile>(path);
|
||||||
|
if (profile != null)
|
||||||
|
{
|
||||||
|
QueueProfileRefresh(profile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 24d3fe24380362140ab8b0a89ecb61e4
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
@@ -5,22 +6,26 @@ namespace InfiniteWorld.Editor
|
|||||||
{
|
{
|
||||||
public class WorldGeneratorEditorWindow : EditorWindow
|
public class WorldGeneratorEditorWindow : EditorWindow
|
||||||
{
|
{
|
||||||
private enum BrushMode
|
private static readonly AutoTileShape[] WallShapeOrder =
|
||||||
{
|
{
|
||||||
Erase,
|
AutoTileShape.OuterTopLeft,
|
||||||
Wall,
|
AutoTileShape.Top,
|
||||||
Environment
|
AutoTileShape.OuterTopRight,
|
||||||
}
|
AutoTileShape.Left,
|
||||||
|
AutoTileShape.Center,
|
||||||
|
AutoTileShape.Right,
|
||||||
|
AutoTileShape.OuterBottomLeft,
|
||||||
|
AutoTileShape.Bottom,
|
||||||
|
AutoTileShape.OuterBottomRight,
|
||||||
|
AutoTileShape.InnerTopLeft,
|
||||||
|
AutoTileShape.InnerTopRight,
|
||||||
|
AutoTileShape.InnerBottomLeft,
|
||||||
|
AutoTileShape.InnerBottomRight
|
||||||
|
};
|
||||||
|
|
||||||
private WorldAutotileProfile profile;
|
private WorldAutotileProfile profile;
|
||||||
private ChunkTemplate template;
|
|
||||||
private SerializedObject serializedProfile;
|
private SerializedObject serializedProfile;
|
||||||
private Vector2 profileScroll;
|
private Vector2 scroll;
|
||||||
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")]
|
[MenuItem("Tools/Infinite World/World Builder")]
|
||||||
public static void Open()
|
public static void Open()
|
||||||
@@ -33,101 +38,9 @@ namespace InfiniteWorld.Editor
|
|||||||
DrawToolbar();
|
DrawToolbar();
|
||||||
EditorGUILayout.Space(6f);
|
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)
|
if (profile == null)
|
||||||
{
|
{
|
||||||
EditorGUILayout.HelpBox("Assign a WorldAutotileProfile to map real tiles onto ground, walls, and environment.", MessageType.Info);
|
EditorGUILayout.HelpBox("Assign or create a WorldAutotileProfile. The workflow is: create the authoring layout prefab, edit tiles inside that grid, then build the final profile back from the layout.", MessageType.Info);
|
||||||
EditorGUILayout.EndVertical();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,163 +50,216 @@ namespace InfiniteWorld.Editor
|
|||||||
}
|
}
|
||||||
|
|
||||||
serializedProfile.Update();
|
serializedProfile.Update();
|
||||||
profileScroll = EditorGUILayout.BeginScrollView(profileScroll);
|
scroll = EditorGUILayout.BeginScrollView(scroll);
|
||||||
|
|
||||||
|
DrawPaletteTools();
|
||||||
|
EditorGUILayout.Space(8f);
|
||||||
|
DrawValidation();
|
||||||
|
EditorGUILayout.Space(8f);
|
||||||
|
DrawProfileFields();
|
||||||
|
|
||||||
|
EditorGUILayout.EndScrollView();
|
||||||
|
serializedProfile.ApplyModifiedProperties();
|
||||||
|
|
||||||
|
if (GUI.changed)
|
||||||
|
{
|
||||||
|
EditorUtility.SetDirty(profile);
|
||||||
|
WorldAutotileProfilePipeline.QueueProfileRefresh(profile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawToolbar()
|
||||||
|
{
|
||||||
|
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
|
||||||
|
profile = (WorldAutotileProfile)EditorGUILayout.ObjectField(profile, typeof(WorldAutotileProfile), false, GUILayout.Width(position.width * 0.55f));
|
||||||
|
|
||||||
|
if (GUILayout.Button("New Profile", EditorStyles.toolbarButton, GUILayout.Width(90f)))
|
||||||
|
{
|
||||||
|
CreateProfileAsset();
|
||||||
|
}
|
||||||
|
|
||||||
|
using (new EditorGUI.DisabledScope(profile == null))
|
||||||
|
{
|
||||||
|
if (GUILayout.Button("Create Authoring Layout", EditorStyles.toolbarButton, GUILayout.Width(155f)))
|
||||||
|
{
|
||||||
|
WorldAutotileProfilePipeline.GenerateAuthoringLayout(profile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorGUILayout.EndHorizontal();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawPaletteTools()
|
||||||
|
{
|
||||||
|
EditorGUILayout.LabelField("Palette Tools", EditorStyles.boldLabel);
|
||||||
|
EditorGUILayout.BeginVertical("box");
|
||||||
|
|
||||||
|
EditorGUILayout.HelpBox("Create Authoring Layout builds a temporary editor-only prefab with marked grid sections. Edit tiles there, then use Build Profile From Layout to write the final WorldAutotileProfile.", MessageType.None);
|
||||||
|
|
||||||
|
EditorGUILayout.PropertyField(serializedProfile.FindProperty("autoUpdatePaletteLayout"), new GUIContent("Auto Update Authoring Layout"));
|
||||||
|
EditorGUILayout.PropertyField(serializedProfile.FindProperty("autoRefreshGeneratedWorld"), new GUIContent("Auto Refresh Generated World"));
|
||||||
|
|
||||||
|
EditorGUILayout.BeginHorizontal();
|
||||||
|
if (GUILayout.Button("Create / Update Layout"))
|
||||||
|
{
|
||||||
|
WorldAutotileProfilePipeline.GenerateAuthoringLayout(profile);
|
||||||
|
}
|
||||||
|
|
||||||
|
using (new EditorGUI.DisabledScope(WorldAutotileProfilePipeline.LoadAuthoringLayoutAsset(profile) == null))
|
||||||
|
{
|
||||||
|
if (GUILayout.Button("Build Profile From Layout"))
|
||||||
|
{
|
||||||
|
WorldAutotileProfilePipeline.BuildProfileFromAuthoringLayout(profile);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GUILayout.Button("Open Layout Prefab"))
|
||||||
|
{
|
||||||
|
GameObject layout = WorldAutotileProfilePipeline.LoadAuthoringLayoutAsset(profile);
|
||||||
|
if (layout != null)
|
||||||
|
{
|
||||||
|
AssetDatabase.OpenAsset(layout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EditorGUILayout.EndHorizontal();
|
||||||
|
|
||||||
|
using (new EditorGUI.DisabledScope(WorldAutotileProfilePipeline.LoadAuthoringLayoutAsset(profile) == null))
|
||||||
|
{
|
||||||
|
if (GUILayout.Button("Ping Layout Prefab"))
|
||||||
|
{
|
||||||
|
GameObject layout = WorldAutotileProfilePipeline.LoadAuthoringLayoutAsset(profile);
|
||||||
|
if (layout != null)
|
||||||
|
{
|
||||||
|
EditorGUIUtility.PingObject(layout);
|
||||||
|
Selection.activeObject = layout;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorGUILayout.LabelField("Layout Path", WorldAutotileProfilePipeline.GetAuthoringLayoutPath(profile));
|
||||||
|
EditorGUILayout.EndVertical();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawValidation()
|
||||||
|
{
|
||||||
|
EditorGUILayout.LabelField("Profile Check", EditorStyles.boldLabel);
|
||||||
|
EditorGUILayout.BeginVertical("box");
|
||||||
|
|
||||||
|
List<string> missingShapes = GetMissingWallShapes();
|
||||||
|
if (missingShapes.Count == 0)
|
||||||
|
{
|
||||||
|
EditorGUILayout.HelpBox("All wall variants are assigned. The generated palette layout will include every corner, side, and center tile.", MessageType.Info);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
EditorGUILayout.HelpBox("Missing wall variants: " + string.Join(", ", missingShapes), MessageType.Warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
int environmentCount = CountAssignedEnvironmentTiles();
|
||||||
|
int prefabCount = CountAssignedRandomPrefabs();
|
||||||
|
EditorGUILayout.LabelField("Background", profile.baseGroundTile != null ? profile.baseGroundTile.name : "Not assigned");
|
||||||
|
EditorGUILayout.LabelField("Environment Tiles", environmentCount.ToString());
|
||||||
|
EditorGUILayout.LabelField("Random Prefabs", prefabCount.ToString());
|
||||||
|
EditorGUILayout.EndVertical();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawProfileFields()
|
||||||
|
{
|
||||||
|
EditorGUILayout.LabelField("Profile Assets", EditorStyles.boldLabel);
|
||||||
|
EditorGUILayout.PropertyField(serializedProfile.FindProperty("baseGroundTile"), new GUIContent("Background Tile"));
|
||||||
|
|
||||||
EditorGUILayout.PropertyField(serializedProfile.FindProperty("baseGroundTile"));
|
|
||||||
EditorGUILayout.Space(6f);
|
EditorGUILayout.Space(6f);
|
||||||
EditorGUILayout.LabelField("Wall Autotile", EditorStyles.miniBoldLabel);
|
EditorGUILayout.LabelField("Wall Variants", EditorStyles.miniBoldLabel);
|
||||||
SerializedProperty walls = serializedProfile.FindProperty("wallTiles");
|
SerializedProperty walls = serializedProfile.FindProperty("wallTiles");
|
||||||
DrawWallGrid(walls);
|
DrawWallGrid(walls);
|
||||||
|
|
||||||
EditorGUILayout.Space(6f);
|
EditorGUILayout.Space(6f);
|
||||||
EditorGUILayout.PropertyField(serializedProfile.FindProperty("environmentTiles"), true);
|
EditorGUILayout.PropertyField(serializedProfile.FindProperty("environmentTiles"), true);
|
||||||
|
|
||||||
EditorGUILayout.EndScrollView();
|
EditorGUILayout.Space(6f);
|
||||||
serializedProfile.ApplyModifiedProperties();
|
EditorGUILayout.PropertyField(serializedProfile.FindProperty("randomPrefabs"), true);
|
||||||
if (GUI.changed)
|
|
||||||
{
|
|
||||||
EditorUtility.SetDirty(profile);
|
|
||||||
}
|
|
||||||
EditorGUILayout.EndVertical();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawWallGrid(SerializedProperty walls)
|
private void DrawWallGrid(SerializedProperty walls)
|
||||||
{
|
{
|
||||||
DrawTriple(walls, "outerTopLeft", "top", "outerTopRight");
|
DrawLabeledRow(walls, AutoTileShape.OuterTopLeft, AutoTileShape.Top, AutoTileShape.OuterTopRight);
|
||||||
DrawTriple(walls, "left", "center", "right");
|
DrawLabeledRow(walls, AutoTileShape.Left, AutoTileShape.Center, AutoTileShape.Right);
|
||||||
DrawTriple(walls, "outerBottomLeft", "bottom", "outerBottomRight");
|
DrawLabeledRow(walls, AutoTileShape.OuterBottomLeft, AutoTileShape.Bottom, AutoTileShape.OuterBottomRight);
|
||||||
DrawDouble(walls, "innerTopLeft", "innerTopRight");
|
DrawLabeledRow(walls, AutoTileShape.InnerTopLeft, AutoTileShape.InnerTopRight);
|
||||||
DrawDouble(walls, "innerBottomLeft", "innerBottomRight");
|
DrawLabeledRow(walls, AutoTileShape.InnerBottomLeft, AutoTileShape.InnerBottomRight);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawTriple(SerializedProperty root, string a, string b, string c)
|
private static void DrawLabeledRow(SerializedProperty root, params AutoTileShape[] shapes)
|
||||||
{
|
{
|
||||||
EditorGUILayout.BeginHorizontal();
|
EditorGUILayout.BeginHorizontal();
|
||||||
EditorGUILayout.PropertyField(root.FindPropertyRelative(a), GUIContent.none);
|
for (int i = 0; i < shapes.Length; i++)
|
||||||
EditorGUILayout.PropertyField(root.FindPropertyRelative(b), GUIContent.none);
|
{
|
||||||
EditorGUILayout.PropertyField(root.FindPropertyRelative(c), GUIContent.none);
|
DrawLabeledCell(root, shapes[i]);
|
||||||
|
}
|
||||||
|
|
||||||
EditorGUILayout.EndHorizontal();
|
EditorGUILayout.EndHorizontal();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawDouble(SerializedProperty root, string a, string b)
|
private static void DrawLabeledCell(SerializedProperty root, AutoTileShape shape)
|
||||||
{
|
{
|
||||||
EditorGUILayout.BeginHorizontal();
|
SerializedProperty property = root.FindPropertyRelative(WorldAutotileEditorLabels.GetPropertyName(shape));
|
||||||
EditorGUILayout.PropertyField(root.FindPropertyRelative(a), GUIContent.none);
|
EditorGUILayout.BeginVertical(GUILayout.MaxWidth(150f));
|
||||||
EditorGUILayout.PropertyField(root.FindPropertyRelative(b), GUIContent.none);
|
EditorGUILayout.LabelField(WorldAutotileEditorLabels.GetShapeLabel(shape), EditorStyles.miniLabel);
|
||||||
EditorGUILayout.EndHorizontal();
|
EditorGUILayout.PropertyField(property, GUIContent.none);
|
||||||
|
EditorGUILayout.EndVertical();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawExitToggle(string label, ChunkExit exit)
|
private List<string> GetMissingWallShapes()
|
||||||
{
|
{
|
||||||
bool current = template.GetExit(exit);
|
List<string> missing = new List<string>();
|
||||||
bool next = GUILayout.Toggle(current, label, "Button");
|
for (int i = 0; i < WallShapeOrder.Length; i++)
|
||||||
if (next == current)
|
|
||||||
{
|
{
|
||||||
return;
|
AutoTileShape shape = WallShapeOrder[i];
|
||||||
}
|
if (profile.wallTiles == null || profile.wallTiles.GetAssignedTile(shape) == null)
|
||||||
|
|
||||||
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);
|
missing.Add(shape.ToString());
|
||||||
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)
|
return missing;
|
||||||
{
|
|
||||||
isPainting = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isPainting)
|
|
||||||
{
|
|
||||||
Repaint();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Color GetCellColor(int x, int y)
|
private int CountAssignedEnvironmentTiles()
|
||||||
{
|
{
|
||||||
if (template.GetWall(x, y))
|
if (profile.environmentTiles == null)
|
||||||
{
|
{
|
||||||
return new Color(0.52f, 0.36f, 0.22f, 1f);
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (template.GetEnvironment(x, y))
|
int count = 0;
|
||||||
|
for (int i = 0; i < profile.environmentTiles.Count; i++)
|
||||||
{
|
{
|
||||||
return new Color(0.2f, 0.54f, 0.25f, 1f);
|
if (profile.environmentTiles[i] != null && profile.environmentTiles[i].tile != null)
|
||||||
|
{
|
||||||
|
count++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Color(0.4f, 0.4f, 0.4f, 1f);
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PaintCell(int x, int y)
|
private int CountAssignedRandomPrefabs()
|
||||||
{
|
{
|
||||||
Undo.RecordObject(template, "Paint Chunk Template");
|
if (profile.randomPrefabs == null)
|
||||||
switch (brushMode)
|
|
||||||
{
|
{
|
||||||
case BrushMode.Erase:
|
return 0;
|
||||||
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<ChunkTemplate>();
|
int count = 0;
|
||||||
asset.Resize(16, 16);
|
for (int i = 0; i < profile.randomPrefabs.Count; i++)
|
||||||
asset.ApplyBorderWallsFromExits(3);
|
{
|
||||||
AssetDatabase.CreateAsset(asset, path);
|
if (profile.randomPrefabs[i] != null && profile.randomPrefabs[i].prefab != null)
|
||||||
AssetDatabase.SaveAssets();
|
{
|
||||||
template = asset;
|
count++;
|
||||||
pendingWidth = asset.width;
|
}
|
||||||
pendingHeight = asset.height;
|
}
|
||||||
Selection.activeObject = asset;
|
|
||||||
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CreateProfileAsset()
|
private void CreateProfileAsset()
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace InfiniteWorld
|
||||||
|
{
|
||||||
|
[DisallowMultipleComponent]
|
||||||
|
public sealed class WorldAutotileAuthoringRoot : MonoBehaviour
|
||||||
|
{
|
||||||
|
public WorldAutotileProfile profile;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: bbd0bbbd74f81084bb8661f761798856
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace InfiniteWorld
|
||||||
|
{
|
||||||
|
public enum WorldAutotileAuthoringSectionType
|
||||||
|
{
|
||||||
|
WallShapes,
|
||||||
|
BackgroundSample,
|
||||||
|
EnvironmentPalette
|
||||||
|
}
|
||||||
|
|
||||||
|
[DisallowMultipleComponent]
|
||||||
|
public sealed class WorldAutotileAuthoringSection : MonoBehaviour
|
||||||
|
{
|
||||||
|
public WorldAutotileAuthoringSectionType sectionType;
|
||||||
|
public Vector2Int size = Vector2Int.one;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: fbfe986c83f0c96419c804643d142e37
|
||||||
Reference in New Issue
Block a user