diff --git a/SuperTiled2Unity/Packages/com.seanba.super-tiled2unity/Editor/Extensions/CustomPropertyExtensions.cs b/SuperTiled2Unity/Packages/com.seanba.super-tiled2unity/Editor/Extensions/CustomPropertyExtensions.cs index 77da52fa..0f117407 100644 --- a/SuperTiled2Unity/Packages/com.seanba.super-tiled2unity/Editor/Extensions/CustomPropertyExtensions.cs +++ b/SuperTiled2Unity/Packages/com.seanba.super-tiled2unity/Editor/Extensions/CustomPropertyExtensions.cs @@ -11,6 +11,7 @@ public static CustomProperty CloneProperty(this CustomProperty property) var cloned = new CustomProperty(); cloned.m_Name = property.m_Name; cloned.m_Type = property.m_Type; + cloned.m_PropertyType = property.m_PropertyType; cloned.m_Value = property.m_Value; return cloned; @@ -37,6 +38,43 @@ public static int CombineFromSource(this List list, List list, string typeName, SuperImportContext importContext) + { + return list.AddPropertiesFromType(typeName, ST2USettings.instance.m_CustomObjectTypes); + } + + public static int ResolveEnumStorageTypes(this List list, List enumTypes) + { + if (list == null || enumTypes.IsEmpty()) + { + return 0; + } + + int numResolved = 0; + foreach (var property in list) + { + if (string.IsNullOrEmpty(property.m_PropertyType)) + { + continue; + } + + CustomEnumType enumType; + if (!enumTypes.TryGetCustomEnumType(property.m_PropertyType, out enumType)) + { + continue; + } + + var storageType = string.IsNullOrEmpty(enumType.m_StorageType) ? "string" : enumType.m_StorageType; + if (!string.Equals(property.m_Type, storageType, StringComparison.OrdinalIgnoreCase)) + { + property.m_Type = storageType; + numResolved++; + } + } + + return numResolved; + } + + public static int AddPropertiesFromType(this List list, string typeName, List objectTypes) { if (list == null) { @@ -49,7 +87,7 @@ public static int AddPropertiesFromType(this List list, string t } CustomObjectType objectType; - if (ST2USettings.instance.m_CustomObjectTypes.TryGetCustomObjectType(typeName, out objectType)) + if (objectTypes.TryGetCustomObjectType(typeName, out objectType)) { return CombineFromSource(list, objectType.m_CustomProperties); } diff --git a/SuperTiled2Unity/Packages/com.seanba.super-tiled2unity/Editor/Importers/TiledAssetImporter.cs b/SuperTiled2Unity/Packages/com.seanba.super-tiled2unity/Editor/Importers/TiledAssetImporter.cs index 26451093..6a08d85a 100644 --- a/SuperTiled2Unity/Packages/com.seanba.super-tiled2unity/Editor/Importers/TiledAssetImporter.cs +++ b/SuperTiled2Unity/Packages/com.seanba.super-tiled2unity/Editor/Importers/TiledAssetImporter.cs @@ -56,6 +56,7 @@ public void AddSuperCustomProperties(GameObject go, XElement xProperties, SuperT // Add properties from our object type (this should be last) properties.AddPropertiesFromType(typeName, SuperImportContext); + properties.ResolveEnumStorageTypes(ST2USettings.instance.m_CustomEnumTypes); // Sort the properties alphabetically component.m_Properties = properties.OrderBy(p => p.m_Name).ToList(); @@ -127,6 +128,7 @@ protected override void InternalOnImportAsset() RendererSorter = new RendererSorter(); SuperImportContext = new SuperImportContext(AssetImportContext, m_PixelsPerUnit, m_EdgesPerEllipse); + RefreshCustomObjectTypes(); } protected override void InternalOnImportAssetCompleted() @@ -174,5 +176,16 @@ protected void AssignUnityLayer(SuperCustomProperties properties) } } } + + protected T GetClassPropertyAs(string className, string propertyName, T defaultValue) where T : IConvertible + { + return TiledCustomTypesJson.GetClassPropertyAs(className, propertyName, defaultValue, ST2USettings.instance.m_CustomObjectTypes); + } + + private void RefreshCustomObjectTypes() + { + ST2USettings.instance.RefreshCustomObjectTypes(); + ST2USettings.instance.RegisterCustomTypeDependencies(AssetImportContext); + } } } diff --git a/SuperTiled2Unity/Packages/com.seanba.super-tiled2unity/Editor/Loaders/CustomPropertyLoader.cs b/SuperTiled2Unity/Packages/com.seanba.super-tiled2unity/Editor/Loaders/CustomPropertyLoader.cs index dabf1205..cfdf1935 100644 --- a/SuperTiled2Unity/Packages/com.seanba.super-tiled2unity/Editor/Loaders/CustomPropertyLoader.cs +++ b/SuperTiled2Unity/Packages/com.seanba.super-tiled2unity/Editor/Loaders/CustomPropertyLoader.cs @@ -31,6 +31,7 @@ public static CustomProperty LoadCustomProperty(XElement xProperty) property.m_Name = xProperty.GetAttributeAs("name", ""); property.m_Type = xProperty.GetAttributeAs("type", "string"); + property.m_PropertyType = xProperty.GetAttributeAs("propertytype", ""); // In some cases, value may be in the default attribute property.m_Value = xProperty.GetAttributeAs("default", ""); diff --git a/SuperTiled2Unity/Packages/com.seanba.super-tiled2unity/Editor/Loaders/Tileset/TilesetLoader.cs b/SuperTiled2Unity/Packages/com.seanba.super-tiled2unity/Editor/Loaders/Tileset/TilesetLoader.cs index f649972b..8f27e6b6 100644 --- a/SuperTiled2Unity/Packages/com.seanba.super-tiled2unity/Editor/Loaders/Tileset/TilesetLoader.cs +++ b/SuperTiled2Unity/Packages/com.seanba.super-tiled2unity/Editor/Loaders/Tileset/TilesetLoader.cs @@ -71,6 +71,7 @@ private void ProcessAttributes(XElement xTileset) } m_SuperTileset.m_CustomProperties = CustomPropertyLoader.LoadCustomPropertyList(xTileset.Element("properties")); + m_SuperTileset.m_CustomProperties.ResolveEnumStorageTypes(ST2USettings.instance.m_CustomEnumTypes); } private void BuildTileset(XElement xTileset) @@ -211,6 +212,7 @@ private void ProcessTileElement(XElement xTile) // Tiles can have custom properties (and properties inherited from their Type) tile.m_CustomProperties = CustomPropertyLoader.LoadCustomPropertyList(xTile.Element("properties")); tile.m_CustomProperties.AddPropertiesFromType(tile.m_Type, m_Importer.SuperImportContext); + tile.m_CustomProperties.ResolveEnumStorageTypes(ST2USettings.instance.m_CustomEnumTypes); // Does the tile have any animation data? var xAnimation = xTile.Element("animation"); @@ -282,6 +284,7 @@ private void ProcessObjectGroupElement(SuperTile tile, XElement xObjectGroup) // Are there any properties on the collision object? collision.m_CustomProperties = CustomPropertyLoader.LoadCustomPropertyList(xObject.Element("properties")); collision.m_CustomProperties.AddPropertiesFromType(collision.m_ObjectType, m_Importer.SuperImportContext); + collision.m_CustomProperties.ResolveEnumStorageTypes(ST2USettings.instance.m_CustomEnumTypes); var xPolygon = xObject.Element("polygon"); var xPolyline = xObject.Element("polyline"); diff --git a/SuperTiled2Unity/Packages/com.seanba.super-tiled2unity/Editor/Properties/CustomObjectType.cs b/SuperTiled2Unity/Packages/com.seanba.super-tiled2unity/Editor/Properties/CustomObjectType.cs index ea1aec24..521b790d 100644 --- a/SuperTiled2Unity/Packages/com.seanba.super-tiled2unity/Editor/Properties/CustomObjectType.cs +++ b/SuperTiled2Unity/Packages/com.seanba.super-tiled2unity/Editor/Properties/CustomObjectType.cs @@ -13,6 +13,15 @@ public class CustomObjectType public List m_CustomProperties; } + [Serializable] + public class CustomEnumType + { + public string m_Name; + public string m_StorageType; + public List m_Values; + public bool m_ValuesAsFlags; + } + public static class CustomObjectTypeExtensions { public static bool TryGetCustomObjectType(this List list, string type, out CustomObjectType customObjectType) @@ -27,4 +36,19 @@ public static bool TryGetCustomObjectType(this List list, stri return customObjectType != null; } } + + public static class CustomEnumTypeExtensions + { + public static bool TryGetCustomEnumType(this List list, string type, out CustomEnumType customEnumType) + { + if (list.IsEmpty()) + { + customEnumType = null; + return false; + } + + customEnumType = list.FirstOrDefault(o => o.m_Name.Equals(type, StringComparison.OrdinalIgnoreCase)); + return customEnumType != null; + } + } } diff --git a/SuperTiled2Unity/Packages/com.seanba.super-tiled2unity/Editor/Properties/TiledCustomTypesJson.cs b/SuperTiled2Unity/Packages/com.seanba.super-tiled2unity/Editor/Properties/TiledCustomTypesJson.cs new file mode 100644 index 00000000..69755261 --- /dev/null +++ b/SuperTiled2Unity/Packages/com.seanba.super-tiled2unity/Editor/Properties/TiledCustomTypesJson.cs @@ -0,0 +1,716 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using UnityEngine; + +namespace SuperTiled2Unity.Editor +{ + public static class TiledCustomTypesJson + { + public static List LoadCustomObjectTypes(string json) + { + if (string.IsNullOrEmpty(json)) + { + return new List(); + } + + try + { + object root = JsonParser.Parse(json); + var propertyTypes = GetPropertyTypes(root); + if (propertyTypes == null) + { + return new List(); + } + + return propertyTypes + .OfType>() + .Where(IsClassType) + .Select(ToCustomObjectType) + .Where(t => !string.IsNullOrEmpty(t.m_Name)) + .ToList(); + } + catch (FormatException) + { + return new List(); + } + } + + public static List LoadCustomEnumTypes(string json) + { + if (string.IsNullOrEmpty(json)) + { + return new List(); + } + + try + { + object root = JsonParser.Parse(json); + var propertyTypes = GetPropertyTypes(root); + if (propertyTypes == null) + { + return new List(); + } + + return propertyTypes + .OfType>() + .Where(IsEnumType) + .Select(ToCustomEnumType) + .Where(t => !string.IsNullOrEmpty(t.m_Name)) + .ToList(); + } + catch (FormatException) + { + return new List(); + } + } + + public static T GetClassPropertyAs( + string className, + string propertyName, + T defaultValue, + List objectTypes) where T : IConvertible + { + CustomObjectType customObjectType; + CustomProperty property; + if (objectTypes.TryGetCustomObjectType(className, out customObjectType) && + customObjectType.m_CustomProperties.TryGetProperty(propertyName, out property)) + { + return ConvertPropertyValue(property.m_Value, defaultValue); + } + + return defaultValue; + } + + public static void MergeInto(List destination, List source) + { + if (destination == null || source == null) + { + return; + } + + foreach (var sourceType in source) + { + if (destination.TryGetCustomObjectType(sourceType.m_Name, out var existingType)) + { + existingType.m_CustomProperties.CombineFromSource(sourceType.m_CustomProperties); + } + else + { + destination.Add(sourceType); + } + } + } + + private static List GetPropertyTypes(object root) + { + var exportedTypes = root as List; + if (exportedTypes != null) + { + return exportedTypes; + } + + var wrapped = root as Dictionary; + if (wrapped != null) + { + object propertyTypes; + if (wrapped.TryGetValue("propertyTypes", out propertyTypes)) + { + return propertyTypes as List; + } + } + + return null; + } + + private static bool IsClassType(Dictionary propertyType) + { + return string.Equals(GetString(propertyType, "type"), "class", StringComparison.OrdinalIgnoreCase); + } + + private static bool IsEnumType(Dictionary propertyType) + { + return string.Equals(GetString(propertyType, "type"), "enum", StringComparison.OrdinalIgnoreCase); + } + + private static CustomObjectType ToCustomObjectType(Dictionary propertyType) + { + return new CustomObjectType + { + m_Name = GetString(propertyType, "name"), + m_Color = ParseColor(GetString(propertyType, "color")), + m_CustomProperties = LoadMembers(GetList(propertyType, "members")) + }; + } + + private static CustomEnumType ToCustomEnumType(Dictionary propertyType) + { + var storageType = GetString(propertyType, "storageType"); + return new CustomEnumType + { + m_Name = GetString(propertyType, "name"), + m_StorageType = string.IsNullOrEmpty(storageType) ? "string" : storageType, + m_Values = LoadEnumValues(GetList(propertyType, "values")), + m_ValuesAsFlags = GetBool(propertyType, "valuesAsFlags") + }; + } + + private static Color ParseColor(string htmlColor) + { + if (!string.IsNullOrEmpty(htmlColor)) + { + try + { + return htmlColor.ToColor(); + } + catch (FormatException) + { + return Color.gray; + } + } + + return Color.gray; + } + + private static List LoadMembers(List members) + { + if (members == null) + { + return new List(); + } + + return members + .OfType>() + .Select(ToCustomProperty) + .Where(p => !p.IsEmpty) + .ToList(); + } + + private static CustomProperty ToCustomProperty(Dictionary member) + { + var typeName = GetString(member, "type"); + + return new CustomProperty + { + m_Name = GetString(member, "name"), + m_Type = string.IsNullOrEmpty(typeName) ? "string" : typeName, + m_PropertyType = GetString(member, "propertyType"), + m_Value = NormalizeValue(GetValue(member, "value")) + }; + } + + private static List LoadEnumValues(List values) + { + if (values == null) + { + return new List(); + } + + return values.Select(NormalizeValue).ToList(); + } + + private static object GetValue(Dictionary data, string key) + { + object value; + return data.TryGetValue(key, out value) ? value : null; + } + + private static string GetString(Dictionary data, string key) + { + var value = GetValue(data, key); + return value as string ?? string.Empty; + } + + private static List GetList(Dictionary data, string key) + { + return GetValue(data, key) as List; + } + + private static bool GetBool(Dictionary data, string key) + { + var value = GetValue(data, key); + if (value is bool) + { + return (bool)value; + } + + var text = value as string; + return text == "1" || string.Equals(text, "true", StringComparison.OrdinalIgnoreCase); + } + + private static string NormalizeValue(object value) + { + if (value == null) + { + return string.Empty; + } + + var text = value as string; + if (text != null) + { + return text; + } + + if (value is bool) + { + return (bool)value ? "true" : "false"; + } + + if (value is IConvertible) + { + return Convert.ToString(value, CultureInfo.InvariantCulture); + } + + return JsonWriter.Write(value); + } + + private static T ConvertPropertyValue(string value, T defaultValue) where T : IConvertible + { + if (typeof(T).IsEnum) + { + var enumValue = value?.Replace("-", "_"); + if (Enum.TryParse(typeof(T), enumValue, true, out var parsedValue)) + { + return (T)parsedValue; + } + + return defaultValue; + } + + if (typeof(T) == typeof(bool)) + { + if (value == "1" || string.Equals(value, "true", StringComparison.OrdinalIgnoreCase)) + { + value = bool.TrueString; + } + else if (value == "0" || string.Equals(value, "false", StringComparison.OrdinalIgnoreCase)) + { + value = bool.FalseString; + } + } + + try + { + return (T)Convert.ChangeType(value, typeof(T), System.Globalization.CultureInfo.InvariantCulture); + } + catch + { + return defaultValue; + } + } + + private class JsonParser + { + private readonly string m_Text; + private int m_Index; + + private JsonParser(string text) + { + m_Text = text; + } + + public static object Parse(string text) + { + var parser = new JsonParser(text); + var value = parser.ParseValue(); + parser.SkipWhitespace(); + if (!parser.IsEnd) + { + throw new FormatException("Unexpected characters after JSON value."); + } + + return value; + } + + private bool IsEnd => m_Index >= m_Text.Length; + + private char Current => IsEnd ? '\0' : m_Text[m_Index]; + + private object ParseValue() + { + SkipWhitespace(); + + if (IsEnd) + { + throw new FormatException("Unexpected end of JSON."); + } + + switch (Current) + { + case '{': + return ParseObject(); + case '[': + return ParseArray(); + case '"': + return ParseString(); + case 't': + ReadLiteral("true"); + return true; + case 'f': + ReadLiteral("false"); + return false; + case 'n': + ReadLiteral("null"); + return null; + default: + return ParseNumber(); + } + } + + private Dictionary ParseObject() + { + var obj = new Dictionary(); + Read('{'); + SkipWhitespace(); + + if (TryRead('}')) + { + return obj; + } + + while (true) + { + SkipWhitespace(); + if (Current != '"') + { + throw new FormatException("Expected JSON object key."); + } + + string key = ParseString(); + SkipWhitespace(); + Read(':'); + obj[key] = ParseValue(); + SkipWhitespace(); + + if (TryRead('}')) + { + return obj; + } + + Read(','); + } + } + + private List ParseArray() + { + var list = new List(); + Read('['); + SkipWhitespace(); + + if (TryRead(']')) + { + return list; + } + + while (true) + { + list.Add(ParseValue()); + SkipWhitespace(); + + if (TryRead(']')) + { + return list; + } + + Read(','); + } + } + + private string ParseString() + { + var builder = new StringBuilder(); + Read('"'); + + while (!IsEnd) + { + char c = m_Text[m_Index++]; + if (c == '"') + { + return builder.ToString(); + } + + if (c != '\\') + { + builder.Append(c); + continue; + } + + if (IsEnd) + { + throw new FormatException("Unfinished JSON string escape."); + } + + char escaped = m_Text[m_Index++]; + switch (escaped) + { + case '"': + case '\\': + case '/': + builder.Append(escaped); + break; + case 'b': + builder.Append('\b'); + break; + case 'f': + builder.Append('\f'); + break; + case 'n': + builder.Append('\n'); + break; + case 'r': + builder.Append('\r'); + break; + case 't': + builder.Append('\t'); + break; + case 'u': + builder.Append(ParseUnicodeEscape()); + break; + default: + throw new FormatException("Invalid JSON string escape."); + } + } + + throw new FormatException("Unterminated JSON string."); + } + + private char ParseUnicodeEscape() + { + if (m_Index + 4 > m_Text.Length) + { + throw new FormatException("Invalid JSON unicode escape."); + } + + string hex = m_Text.Substring(m_Index, 4); + m_Index += 4; + return (char)int.Parse(hex, NumberStyles.HexNumber, CultureInfo.InvariantCulture); + } + + private object ParseNumber() + { + int start = m_Index; + + if (Current == '-') + { + m_Index++; + } + + while (char.IsDigit(Current)) + { + m_Index++; + } + + bool isFloat = false; + if (Current == '.') + { + isFloat = true; + m_Index++; + while (char.IsDigit(Current)) + { + m_Index++; + } + } + + if (Current == 'e' || Current == 'E') + { + isFloat = true; + m_Index++; + if (Current == '+' || Current == '-') + { + m_Index++; + } + + while (char.IsDigit(Current)) + { + m_Index++; + } + } + + if (start == m_Index) + { + throw new FormatException("Expected JSON value."); + } + + string number = m_Text.Substring(start, m_Index - start); + if (isFloat) + { + double parsedDouble; + if (double.TryParse(number, NumberStyles.Float, CultureInfo.InvariantCulture, out parsedDouble)) + { + return parsedDouble; + } + } + else + { + long parsedLong; + if (long.TryParse(number, NumberStyles.Integer, CultureInfo.InvariantCulture, out parsedLong)) + { + return parsedLong; + } + } + + throw new FormatException("Invalid JSON number."); + } + + private void ReadLiteral(string literal) + { + for (int i = 0; i < literal.Length; i++) + { + Read(literal[i]); + } + } + + private void Read(char expected) + { + if (IsEnd || m_Text[m_Index] != expected) + { + throw new FormatException(string.Format("Expected '{0}'.", expected)); + } + + m_Index++; + } + + private bool TryRead(char expected) + { + if (!IsEnd && m_Text[m_Index] == expected) + { + m_Index++; + return true; + } + + return false; + } + + private void SkipWhitespace() + { + while (!IsEnd && char.IsWhiteSpace(Current)) + { + m_Index++; + } + } + } + + private static class JsonWriter + { + public static string Write(object value) + { + var builder = new StringBuilder(); + WriteValue(builder, value); + return builder.ToString(); + } + + private static void WriteValue(StringBuilder builder, object value) + { + if (value == null) + { + builder.Append("null"); + return; + } + + var text = value as string; + if (text != null) + { + WriteString(builder, text); + return; + } + + if (value is bool) + { + builder.Append((bool)value ? "true" : "false"); + return; + } + + var list = value as List; + if (list != null) + { + WriteArray(builder, list); + return; + } + + var obj = value as Dictionary; + if (obj != null) + { + WriteObject(builder, obj); + return; + } + + builder.Append(Convert.ToString(value, CultureInfo.InvariantCulture)); + } + + private static void WriteArray(StringBuilder builder, List list) + { + builder.Append('['); + for (int i = 0; i < list.Count; i++) + { + if (i > 0) + { + builder.Append(','); + } + + WriteValue(builder, list[i]); + } + + builder.Append(']'); + } + + private static void WriteObject(StringBuilder builder, Dictionary obj) + { + builder.Append('{'); + bool needsComma = false; + foreach (var pair in obj) + { + if (needsComma) + { + builder.Append(','); + } + + WriteString(builder, pair.Key); + builder.Append(':'); + WriteValue(builder, pair.Value); + needsComma = true; + } + + builder.Append('}'); + } + + private static void WriteString(StringBuilder builder, string text) + { + builder.Append('"'); + foreach (char c in text) + { + switch (c) + { + case '"': + builder.Append("\\\""); + break; + case '\\': + builder.Append("\\\\"); + break; + case '\b': + builder.Append("\\b"); + break; + case '\f': + builder.Append("\\f"); + break; + case '\n': + builder.Append("\\n"); + break; + case '\r': + builder.Append("\\r"); + break; + case '\t': + builder.Append("\\t"); + break; + default: + builder.Append(c); + break; + } + } + + builder.Append('"'); + } + } + } +} diff --git a/SuperTiled2Unity/Packages/com.seanba.super-tiled2unity/Editor/Properties/TiledCustomTypesJson.cs.meta b/SuperTiled2Unity/Packages/com.seanba.super-tiled2unity/Editor/Properties/TiledCustomTypesJson.cs.meta new file mode 100644 index 00000000..75b1a6e5 --- /dev/null +++ b/SuperTiled2Unity/Packages/com.seanba.super-tiled2unity/Editor/Properties/TiledCustomTypesJson.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: ea9e9dd0ea70bc245a234e37b0c3ff49 \ No newline at end of file diff --git a/SuperTiled2Unity/Packages/com.seanba.super-tiled2unity/Editor/Settings/CustomPropertiesWindow.cs b/SuperTiled2Unity/Packages/com.seanba.super-tiled2unity/Editor/Settings/CustomPropertiesWindow.cs index 7ffa90bb..b6b28c57 100644 --- a/SuperTiled2Unity/Packages/com.seanba.super-tiled2unity/Editor/Settings/CustomPropertiesWindow.cs +++ b/SuperTiled2Unity/Packages/com.seanba.super-tiled2unity/Editor/Settings/CustomPropertiesWindow.cs @@ -6,7 +6,8 @@ namespace SuperTiled2Unity.Editor { public class CustomPropertiesWindow : EditorWindow { - private static readonly GUIContent m_TitleContent = new GUIContent("Custom Object Types Properties", "These are the objects created in the Object Types Editor in Tiled."); + private static readonly GUIContent m_TitleContent = new GUIContent("Loaded Custom Type Properties", "These are the custom type defaults loaded from the configured Tiled type assets."); + private static readonly GUIContent m_EnumTitleContent = new GUIContent("Loaded Custom Enum Types", "These are the custom enum definitions loaded from the configured Tiled type assets."); private Vector2 m_ScrollPosition; @@ -36,6 +37,18 @@ private void OnGUICustomProperties() OnGUICustomObjectType(obj); EditorGUILayout.Space(); } + + if (!ST2USettings.instance.m_CustomEnumTypes.IsEmpty()) + { + EditorGUILayout.Space(); + EditorGUILayout.LabelField(m_EnumTitleContent, EditorStyles.boldLabel); + + foreach (var enumType in ST2USettings.instance.m_CustomEnumTypes) + { + EditorGUILayout.Space(); + OnGUICustomEnumType(enumType); + } + } } private void OnGUICustomObjectType(CustomObjectType obj) @@ -44,7 +57,7 @@ private void OnGUICustomObjectType(CustomObjectType obj) { EditorGUILayout.LabelField(obj.m_Name); GUI.enabled = false; - EditorGUILayout_ColorFieldNoEdit(GUIContent.none, obj.m_Color); + EditorGUILayout_ColorFieldNoEdit(new GUIContent("Color"), obj.m_Color); GUI.enabled = true; EditorGUILayout.Space(); @@ -62,6 +75,7 @@ private void OnGUICustomObjectType(CustomObjectType obj) EditorGUILayout.LabelField(customProperty.m_Name); EditorGUILayout.LabelField(customProperty.m_Value, EditorStyles.textField); EditorGUILayout.LabelField(customProperty.m_Type); + EditorGUILayout.LabelField(customProperty.m_PropertyType); } } } @@ -70,6 +84,20 @@ private void OnGUICustomObjectType(CustomObjectType obj) } } + private void OnGUICustomEnumType(CustomEnumType enumType) + { + using (new GUILayout.VerticalScope(EditorStyles.helpBox)) + { + EditorGUILayout.LabelField(enumType.m_Name); + using (new GuiScopedIndent()) + { + EditorGUILayout.LabelField("Storage Type", enumType.m_StorageType); + EditorGUILayout.Toggle("Values As Flags", enumType.m_ValuesAsFlags); + EditorGUILayout.LabelField("Values", string.Join(", ", enumType.m_Values)); + } + } + } + private void OnGUIButtons() { EditorGUILayout.Space(); @@ -94,5 +122,6 @@ internal static void EditorGUILayout_ColorFieldNoEdit(GUIContent label, Color co { EditorGUILayout.ColorField(label, color, false, true, false); } + } } diff --git a/SuperTiled2Unity/Packages/com.seanba.super-tiled2unity/Editor/Settings/ST2USettings.cs b/SuperTiled2Unity/Packages/com.seanba.super-tiled2unity/Editor/Settings/ST2USettings.cs index 3ff9e1aa..ada93ed7 100644 --- a/SuperTiled2Unity/Packages/com.seanba.super-tiled2unity/Editor/Settings/ST2USettings.cs +++ b/SuperTiled2Unity/Packages/com.seanba.super-tiled2unity/Editor/Settings/ST2USettings.cs @@ -17,6 +17,7 @@ public class ST2USettings : ScriptableSingleton public Material m_DefaultMaterial = null; public List m_MaterialMatchings = new List(); public TextAsset m_ObjectTypesXml = null; + public TextAsset m_ObjectTypesJson = null; public string m_ParseXmlError = string.Empty; public CompositeCollider2D.GeometryType m_CollisionGeometryType = CompositeCollider2D.GeometryType.Polygons; @@ -57,53 +58,122 @@ public class ST2USettings : ScriptableSingleton }; public List m_CustomObjectTypes; + public List m_CustomEnumTypes; public List m_PrefabReplacements = new List(); - // Invoke this to ensure that Xml Object Types are up-to-date - // Our importers that depend on Object Types from tiled will want to call this early in their import process internal void RefreshCustomObjectTypes() { m_CustomObjectTypes = new List(); + m_CustomEnumTypes = new List(); m_ParseXmlError = string.Empty; if (m_ObjectTypesXml != null) { - try - { - XDocument xdoc = XDocument.Parse(m_ObjectTypesXml.text); + RefreshObjectTypesXml(); + } - if (xdoc.Root.Name != "objecttypes") - { - m_ParseXmlError = string.Format("'{0}' is not a valid object types xml file.", m_ObjectTypesXml.name); - } + if (m_ObjectTypesJson != null) + { + RefreshObjectTypesJson(); + } + } - // Import the data from the objecttype elements - foreach (var xObjectType in xdoc.Descendants("objecttype")) - { - var cot = new CustomObjectType - { - m_Name = xObjectType.GetAttributeAs("name", "NoName"), - m_Color = xObjectType.GetAttributeAsColor("color", Color.gray), - m_CustomProperties = CustomPropertyLoader.LoadCustomPropertyList(xObjectType) - }; - - m_CustomObjectTypes.Add(cot); - } + internal void RegisterCustomTypeDependencies(UnityEditor.AssetImporters.AssetImportContext context) + { + RegisterTextAssetDependency(context, m_ObjectTypesXml); + RegisterTextAssetDependency(context, m_ObjectTypesJson); + } + + private void RefreshObjectTypesXml() + { + try + { + XDocument xdoc = XDocument.Parse(m_ObjectTypesXml.text); + + if (xdoc.Root.Name != "objecttypes") + { + m_ParseXmlError = string.Format("'{0}' is not a valid object types xml file.", m_ObjectTypesXml.name); } - catch (XmlException xe) + + // Import the data from the objecttype elements + foreach (var xObjectType in xdoc.Descendants("objecttype")) { - m_ParseXmlError = string.Format("'{0}' is not a valid XML file.\n\nError: {1}", m_ObjectTypesXml.name, xe.Message); - m_CustomObjectTypes.Clear(); + var cot = new CustomObjectType + { + m_Name = xObjectType.GetAttributeAs("name", "NoName"), + m_Color = xObjectType.GetAttributeAsColor("color", Color.gray), + m_CustomProperties = CustomPropertyLoader.LoadCustomPropertyList(xObjectType) + }; + + AddOrMergeCustomObjectType(cot); } - catch (Exception e) + } + catch (XmlException xe) + { + m_ParseXmlError = string.Format("'{0}' is not a valid XML file.\n\nError: {1}", m_ObjectTypesXml.name, xe.Message); + m_CustomObjectTypes.Clear(); + } + catch (Exception e) + { + m_ParseXmlError = e.Message; + m_CustomObjectTypes.Clear(); + } + } + + private void RefreshObjectTypesJson() + { + var customTypes = TiledCustomTypesJson.LoadCustomObjectTypes(m_ObjectTypesJson.text); + var customEnumTypes = TiledCustomTypesJson.LoadCustomEnumTypes(m_ObjectTypesJson.text); + if (customTypes.Count == 0 && customEnumTypes.Count == 0 && !string.IsNullOrWhiteSpace(m_ObjectTypesJson.text)) + { + m_ParseXmlError = string.Format("'{0}' does not contain Tiled custom class or enum definitions.", m_ObjectTypesJson.name); + } + + TiledCustomTypesJson.MergeInto(m_CustomObjectTypes, customTypes); + MergeCustomEnumTypes(customEnumTypes); + } + + private void AddOrMergeCustomObjectType(CustomObjectType customObjectType) + { + if (m_CustomObjectTypes.TryGetCustomObjectType(customObjectType.m_Name, out var existingType)) + { + existingType.m_CustomProperties.CombineFromSource(customObjectType.m_CustomProperties); + } + else + { + m_CustomObjectTypes.Add(customObjectType); + } + } + + private void MergeCustomEnumTypes(List customEnumTypes) + { + foreach (var customEnumType in customEnumTypes) + { + if (!m_CustomEnumTypes.TryGetCustomEnumType(customEnumType.m_Name, out _)) { - m_ParseXmlError = e.Message; - m_CustomObjectTypes.Clear(); + m_CustomEnumTypes.Add(customEnumType); } } } + private static void RegisterTextAssetDependency(UnityEditor.AssetImporters.AssetImportContext context, TextAsset textAsset) + { + if (context == null || textAsset == null) + { + return; + } + + var path = AssetDatabase.GetAssetPath(textAsset); + if (string.IsNullOrEmpty(path)) + { + return; + } + + context.DependsOnSourceAsset(path); + context.DependsOnArtifact(path); + } + internal void SortMaterialMatchings() { m_MaterialMatchings = m_MaterialMatchings.OrderBy(m => m.m_LayerName).ToList(); diff --git a/SuperTiled2Unity/Packages/com.seanba.super-tiled2unity/Editor/Settings/ST2USettingsProvider.cs b/SuperTiled2Unity/Packages/com.seanba.super-tiled2unity/Editor/Settings/ST2USettingsProvider.cs index fdebc73c..a7c864c9 100644 --- a/SuperTiled2Unity/Packages/com.seanba.super-tiled2unity/Editor/Settings/ST2USettingsProvider.cs +++ b/SuperTiled2Unity/Packages/com.seanba.super-tiled2unity/Editor/Settings/ST2USettingsProvider.cs @@ -181,7 +181,7 @@ private void DoGuiPrefabReplacements() using (new GUILayout.HorizontalScope()) { GUILayout.FlexibleSpace(); - if (GUILayout.Button("Add From Object Types Xml")) + if (GUILayout.Button("Add From Custom Types")) { ST2USettings.instance.AddObjectsToPrefabReplacements(); EditorUtility.SetDirty(ST2USettings.instance); @@ -232,10 +232,14 @@ private void DoGuiColliders() private void DoCustomPropertySettings() { var xmlProperty = m_SerializedObject.FindProperty(nameof(ST2USettings.m_ObjectTypesXml)); + var jsonProperty = m_SerializedObject.FindProperty(nameof(ST2USettings.m_ObjectTypesJson)); EditorGUILayout.LabelField("Custom Property Settings", EditorStyles.boldLabel); xmlProperty.objectReferenceValue = EditorGUILayout.ObjectField(SettingsContent.ObjectTypesXmlContent, xmlProperty.objectReferenceValue, typeof(TextAsset), false); + jsonProperty.objectReferenceValue = EditorGUILayout.ObjectField(SettingsContent.CustomTypesJsonContent, jsonProperty.objectReferenceValue, typeof(TextAsset), false); + + EditorGUILayout.HelpBox("For Tiled custom classes, export a .json file from Tiled's Custom Types Editor and assign it here. Class members become default custom properties, while class colors are used for Scene View gizmo colors. Reimport Tiled assets after changing custom type defaults.", MessageType.Info); if (!string.IsNullOrEmpty(ST2USettings.instance.m_ParseXmlError)) { @@ -373,7 +377,8 @@ private class SettingsContent public static readonly GUIContent AnimationFramerateContent = new GUIContent("Animation Framerate", "How many frames per second for tile animations."); public static readonly GUIContent DefaultMaterialContent = new GUIContent("Default Material", "Set to the material you want to use for sprites and tiles imported by SuperTiled2Unity. Leave empy to use built-in sprite material."); public static readonly GUIContent MaterialMatchingsContent = new GUIContent("Material Matchings", "Match these materials by Tiled Layer names."); - public static readonly GUIContent ObjectTypesXmlContent = new GUIContent("Object Types Xml", "Set to an Object Types Xml file exported from Tiled Object Type Editor."); + public static readonly GUIContent ObjectTypesXmlContent = new GUIContent("Object Types XML", "Set to an Object Types XML file exported from Tiled Object Type Editor."); + public static readonly GUIContent CustomTypesJsonContent = new GUIContent("Custom Types JSON", "Set to a Custom Types JSON file exported from Tiled Custom Types Editor. Class members become default custom properties and class colors are used for Scene View gizmos."); public static readonly GUIContent PrefabReplacmentsContent = new GUIContent("Prefab Replacements", "List of prefabs to replace Tiled Object Types during import."); public static readonly GUIContent CollisionGeometryTypeContent = new GUIContent("Collision Geometry Type", "The type of geometry used by CompositeCollider2D components."); public static readonly GUIContent LayerColorsContent = new GUIContent("Layer Colors", "These colors will be used for drawing colliders in your imported Tiled maps."); diff --git a/SuperTiled2Unity/Packages/com.seanba.super-tiled2unity/Runtime/CustomProperty.cs b/SuperTiled2Unity/Packages/com.seanba.super-tiled2unity/Runtime/CustomProperty.cs index 739c88c4..c388454e 100644 --- a/SuperTiled2Unity/Packages/com.seanba.super-tiled2unity/Runtime/CustomProperty.cs +++ b/SuperTiled2Unity/Packages/com.seanba.super-tiled2unity/Runtime/CustomProperty.cs @@ -8,6 +8,7 @@ public class CustomProperty { public string m_Name; public string m_Type; + public string m_PropertyType; public string m_Value; public bool IsEmpty => string.IsNullOrEmpty(m_Name);