Skip to content

Instantly share code, notes, and snippets.

@LuizMoratelli
Last active March 19, 2024 22:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save LuizMoratelli/05aba2a62a37828960ba3712c6bc1c2e to your computer and use it in GitHub Desktop.
Save LuizMoratelli/05aba2a62a37828960ba3712c6bc1c2e to your computer and use it in GitHub Desktop.
[TCG Engine] 🤝 [Odin]

1 Adding Attributes to Data classes

1.1 CardData.cs

  [HorizontalGroup("Info", Width = 325)]
  [VerticalGroup("Info/Side1", Order =2), LabelWidth(50)]
  public string id;

  [VerticalGroup("Info/Side1"), LabelWidth(50)]
  public string title;
  [VerticalGroup("Info/Side2"), PreviewField(100), HideLabel,]
  public Sprite art_full;
  [VerticalGroup("Info/Side2"), PreviewField(100), HideLabel,]
  public Sprite art_board;

  [VerticalGroup("Info/Side1"), LabelWidth(50)]
  public CardType type;
  [VerticalGroup("Info/Side1"), LabelWidth(50)]
  [InlineButton("@CardEditor.CreateNewAction($root, $property)", SdfIconType.PlusCircleDotted, "")]
  public TeamData team;
  [VerticalGroup("Info/Side1"), LabelWidth(50)]
  [InlineButton("@CardEditor.CreateNewAction($root, $property)", SdfIconType.PlusCircleDotted, "")]
  public RarityData rarity;
  [HorizontalGroup("Info/Side1/Stats"), LabelWidth(30), LabelText(SdfIconType.DiamondFill, Text = "")]
  public int mana;
  [HorizontalGroup("Info/Side1/Stats"), LabelWidth(30), LabelText(SdfIconType.Hammer, Text = "")]
  public int attack;
  [HorizontalGroup("Info/Side1/Stats"), LabelWidth(30), LabelText(SdfIconType.DropletFill, Text = "")]
  public int hp;

  [TabGroup("Traits", Icon = SdfIconType.Speedometer), ListDrawerSettings]
  public TraitData[] traits;
  [TabGroup("Traits", Icon = SdfIconType.Speedometer)]
  public TraitStat[] stats;

  [TabGroup("Abilities", Icon = SdfIconType.Magic), ListDrawerSettings]
  public AbilityData[] abilities;

  [VerticalGroup("Info/Side1"), LabelWidth(50)]
  [TextArea(3, 5)]
  public string text;

  [VerticalGroup("Info/Side1"), LabelWidth(50)]
  [TextArea(5, 10)]
  public string desc;

  [TabGroup("FX", Icon = SdfIconType.Stars)]
  public GameObject spawn_fx;
  [TabGroup("FX", Icon = SdfIconType.Stars)]
  public GameObject death_fx;
  [TabGroup("FX", Icon = SdfIconType.Stars)]
  public GameObject attack_fx;
  [TabGroup("FX", Icon = SdfIconType.Stars)]
  public GameObject damage_fx;
  [TabGroup("FX", Icon = SdfIconType.Stars)]
  public GameObject idle_fx;
  [TabGroup("FX", Icon = SdfIconType.Stars)]
  public AudioClip spawn_audio;
  [TabGroup("FX", Icon = SdfIconType.Stars)]
  public AudioClip death_audio;
  [TabGroup("FX", Icon = SdfIconType.Stars)]
  public AudioClip attack_audio;
  [TabGroup("FX", Icon = SdfIconType.Stars)]
  public AudioClip damage_audio;
  [TabGroup("FX", Icon = SdfIconType.Stars)]

  [TabGroup("Availability", Icon = SdfIconType.Basket3Fill)]
  public bool deckbuilding = false;
  [TabGroup("Availability", Icon = SdfIconType.Basket3Fill)]
  public int cost = 100;
  [TabGroup("Availability", Icon = SdfIconType.Basket3Fill)]
  public PackData[] packs;
  //...

1.2 AbilityData.cs

[InlineButton("@CardDataProcessor.CloneAbility($root, $value)", SdfIconType.FileEarmarkPlusFill, "")]
public class AbilityData : ScriptableObject
{
  public string id;
  [TabGroup("Trigger", Icon = SdfIconType.Alarm), EnumToggleButtons, HideLabel]
  public AbilityTrigger trigger;             //WHEN does the ability trigger?
  [TabGroup("Trigger", Icon = SdfIconType.Alarm), ListDrawerSettings]
  public ConditionData[] conditions_trigger; //Condition checked on the card triggering the ability (usually the caster)

  [TabGroup("Target", Icon = SdfIconType.Capslock), EnumToggleButtons, HideLabel]
  public AbilityTarget target;               //WHO is targeted?
  [TabGroup("Target", Icon = SdfIconType.Capslock), ListDrawerSettings]
  public ConditionData[] conditions_target;  //Condition checked on the target to know if its a valid taget
  [TabGroup("Target", Icon = SdfIconType.Capslock), ListDrawerSettings]
  public FilterData[] filters_target;  //Condition checked on the target to know if its a valid taget

  [TabGroup("Effect", Icon = SdfIconType.Lightning), ListDrawerSettings]
  public EffectData[] effects;              //WHAT this does?
  [TabGroup("Effect", Icon = SdfIconType.Lightning), ListDrawerSettings]
  public StatusData[] status;               //Status added by this ability  
  [TabGroup("Effect", Icon = SdfIconType.Lightning)]
  public int value;                         //Value passed to the effect (deal X damage)
  [TabGroup("Effect", Icon = SdfIconType.Lightning)]
  public int duration;                      //Duration passed to the effect (usually for status, 0=permanent)

  [TabGroup("Chain or Choices", Icon = SdfIconType.SignpostSplit), ListDrawerSettings]
  public AbilityData[] chain_abilities;    //Abilities that will be triggered after this one

  [Header("Activated Ability")]
  public int mana_cost;                   //Mana cost for  activated abilities
  public bool exhaust;                    //Action cost for activated abilities

  [TabGroup("FX", Icon = SdfIconType.Stars)]
  public GameObject board_fx;
  [TabGroup("FX", Icon = SdfIconType.Stars)]
  public GameObject caster_fx;
  [TabGroup("FX", Icon = SdfIconType.Stars)]
  public GameObject target_fx;
  [TabGroup("FX", Icon = SdfIconType.Stars)]
  public AudioClip cast_audio;
  [TabGroup("FX", Icon = SdfIconType.Stars)]
  public AudioClip target_audio;
  [TabGroup("FX", Icon = SdfIconType.Stars)]
  public bool charge_target;

  [Header("Text")]
  public string title;
  [TextArea(5, 7)]
  public string desc;
  //...
}

1.3 ConditionData

[InlineButton("@CardEditor.CloneAction($root, $property, $value)", SdfIconType.FileEarmarkPlusFill, "", ShowIf = "@CardEditor.ShowNotNull($value)")]
public class ConditionData : ScriptableObject

1.4 FilterData

[InlineButton("@CardEditor.CloneAction($root, $property, $value)", SdfIconType.FileEarmarkPlusFill, "", ShowIf = "@CardEditor.ShowNotNull($value)")]
public class FilterData : ScriptableObject

1.5 EffectData

[InlineButton("@CardEditor.CloneAction($root, $property, $value)", SdfIconType.FileEarmarkPlusFill, "", ShowIf = "@CardEditor.ShowNotNull($value)")]
public class EffectData : ScriptableObject

1.6 StatusData

[InlineButton("@CardEditor.CloneAction($root, $property, $value)", SdfIconType.FileEarmarkPlusFill, "", ShowIf = "@CardEditor.ShowNotNull($value)")]
public class StatusData : ScriptableObject

1.7 TraitData

[InlineButton("@CardEditor.CloneAction($root, $property, $value)", SdfIconType.FileEarmarkPlusFill, "", ShowIf = "@CardEditor.ShowNotNull($value)")]
public class TraitData : ScriptableObject

1.8 RarityData

[InlineButton("@CardEditor.CloneAction($root, $property, $value)", SdfIconType.FileEarmarkPlusFill, "", ShowIf = "@CardEditor.ShowNotNull($value)")]
public class RarityData : ScriptableObject

1.9 Results


image

image

2 CardEditor (Create this file inside some Editor folder)

2.1 Script

using Sirenix.OdinInspector.Editor;
using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using TcgEngine;
using Sirenix.Utilities.Editor;
using Sirenix.OdinInspector.Demos.RPGEditor;
using Sirenix.OdinInspector;
using System.Reflection;
using Sirenix.Utilities;
using Unity.VisualScripting;
using System.Linq;


public class DuplicateFinder : OdinMenuEditorWindow
{
    [MenuItem("Tools/Moratelli/Duplicates Finder")]
    private static void OpenEditor() => GetWindow<DuplicateFinder>();


    protected override OdinMenuTree BuildMenuTree()
    {
        var tree = new OdinMenuTree();
        tree.Config.DrawSearchToolbar = true;

        var assets = Resources.LoadAll("")
            .Where(obj => obj.GetType().GetField("id") != null)
            .OrderBy(obj => obj.ToString())
            .GroupBy(x => new { id = x.GetType().GetField("id").GetValue(x), type = x.GetType().Name })
            .Where(group => group.Sum(x => 1) > 1);

        if (assets.Count() == 0)
        {
            tree.Add("Everything good!", null, SdfIconType.HandThumbsUpFill);
        }
        else
        {
            tree.Add("There are duplications!", null, SdfIconType.HandThumbsDownFill);
        }

        foreach (var group in assets)
        {
            foreach (var item in group)
            {
                tree.Add($"{group.Key.id}/{item.name}", item);

            }
        }

        return tree;
    }
}

public class CardFinderForm
{
    [VerticalGroup("Form", PaddingBottom = 50)]
    public string cardName = "";
    [VerticalGroup("Form")]
    public AbilityData selectedAbility = null;
    [VerticalGroup("Form")]
    public AbilityTrigger selectedTrigger = AbilityTrigger.None;
    [VerticalGroup("Form")]
    public TeamData selectedTeam = null;
    [VerticalGroup("Form")]
    public RarityData selectedRarity = null;
    [VerticalGroup("Form")]
    [TextArea(3, 5)]
    public string text = "";
    [VerticalGroup("Form")]
    public TraitData selectedTrait = null;
    [VerticalGroup("Form")]
    public PackData selectedPack = null;

    private OdinMenuEditorWindow parent;

    public CardFinderForm(OdinMenuEditorWindow parent)
    {
        this.parent = parent;
    }

    [VerticalGroup("Actions")]
    [HorizontalGroup("Actions/Filter")]
    [Button(ButtonSizes.Large, Icon = SdfIconType.TrashFill)]
    private void ClearFilters()
    {
        selectedTrigger = AbilityTrigger.None;
        selectedAbility = null;
        cardName = "";
        selectedTeam = null;
        selectedRarity = null;
        text = "";
        selectedTrait = null;
        selectedPack = null;
        parent.ForceMenuTreeRebuild();
    }

    [HorizontalGroup("Actions/Filter")]
    [Button(ButtonSizes.Large, Icon = SdfIconType.FunnelFill)]
    private void Filter()
    {
        parent.ForceMenuTreeRebuild();
    }
}

public class CardFinder : OdinMenuEditorWindow
{
    [MenuItem("Tools/Moratelli/Card Finder")]
    private static void OpenEditor() => GetWindow<CardFinder>();

    private CardFinderForm form;
    private CardData[] cardDataAssets;

    protected override OdinMenuTree BuildMenuTree()
    {
        var tree = new OdinMenuTree();
        form ??= new CardFinderForm(this);
        tree.Config.DrawSearchToolbar = true;
        tree.Add("Filters", form);

        cardDataAssets = Resources.LoadAll<CardData>("").OrderBy(card => card.name).ToArray();

        foreach (CardData cardData in cardDataAssets)
        {

            bool show = true;
            if (form.selectedAbility != null && cardData.abilities.FirstOrDefault(ability => ability.id == form.selectedAbility.id) == null)
            {
                show = false;
            }

            if (form.selectedTrigger != AbilityTrigger.None && cardData.abilities.FirstOrDefault(ability => ability.trigger == form.selectedTrigger) == null)
            {
                show = false;
            }

            if (form.cardName != "" && cardData.name.ToLower().Contains(form.cardName.ToLower()) == false)
            {
                show = false;
            }

            if (form.text != "" && cardData.text.ToLower().Contains(form.text.ToLower()) == false)
            {
                show = false;
            }

            if (form.selectedPack != null && cardData.packs.FirstOrDefault(pack => pack.id == form.selectedPack.id) == null)
            {
                show = false;
            }

            if (form.selectedTeam != null && cardData.team.id != form.selectedTeam.id)
            {
                show = false;
            }

            if (form.selectedTrait != null && cardData.traits.FirstOrDefault(pack => pack.id == form.selectedTrait.id) == null)
            {
                show = false;
            }

            if (form.selectedRarity != null && cardData.rarity.id != form.selectedRarity.id)
            {
                show = false;
            }


            if (show)
            {
                tree.Add($"Card/{cardData.name}", cardData);
            }
        }

        return tree;
    }

    protected override void OnBeginDrawEditors()
    {
        if (MenuTree == null) return;

        SirenixEditorGUI.BeginHorizontalToolbar(MenuTree.Config.SearchToolbarHeight);
        {

            if (SirenixEditorGUI.ToolbarButton(new GUIContent("Clone")))
            {
                var selected = MenuTree.Selection.SelectedValue;

                if (selected == null || !selected.GetType().IsSubclassOf(typeof(ScriptableObject))) return;

                Type selectedType = selected.GetType();

                var objToSelect = EditorUtils.Clone(selectedType, selected);

                if (objToSelect != null)
                {
                    TrySelectMenuItemWithObject(objToSelect);
                }
            }
        }
        SirenixEditorGUI.EndHorizontalToolbar();
    }
}


public class ScriptableObjectFinderForm
{
    private string[] types;
    [ValueDropdown("types")]

    [VerticalGroup("Form", PaddingBottom = 50)]
    public string selectedType = "";

    private OdinMenuEditorWindow parent;


    public ScriptableObjectFinderForm(OdinMenuEditorWindow parent)
    {
        types = typeof(CardData)
            .Assembly.GetTypes()
            .Where(type => type.IsSubclassOf(typeof(ScriptableObject)))
            .Select(type => type.ToString()).ToArray();
        this.parent = parent;
    }

    [VerticalGroup("Actions")]
    [HorizontalGroup("Actions/Filter")]
    [Button(ButtonSizes.Large, Icon = SdfIconType.TrashFill)]
    private void ClearFilters()
    {
        selectedType = "";
        parent.ForceMenuTreeRebuild();
    }

    [HorizontalGroup("Actions/Filter")]
    [Button(ButtonSizes.Large, Icon = SdfIconType.FunnelFill)]
    private void Filter()
    {
        parent.ForceMenuTreeRebuild();
    }
}

public class ScriptableObjectFinder : OdinMenuEditorWindow
{
    [MenuItem("Tools/Moratelli/SO Finder")]
    private static void OpenEditor() => GetWindow<ScriptableObjectFinder>();

    private ScriptableObjectFinderForm form;

    protected override OdinMenuTree BuildMenuTree()
    {
        var tree = new OdinMenuTree();
        form ??= new ScriptableObjectFinderForm(this);
        tree.Config.DrawSearchToolbar = true;
        tree.Add("Filters", form);
        var asm = typeof(ConditionData).Assembly;

        ScriptableObject[] assets = Resources.LoadAll<ScriptableObject>("").OrderBy(obj => obj.ToString()).ToArray();

        foreach (ScriptableObject asset in assets)
        {
            bool show = true;

            if (asset.GetType().Name.Contains("TcgEngine."))
            {
                show = false;
            }

            if (form.selectedType != "")
            {
                var search = tree.Config.SearchTerm;
                var type = asm.GetType(form.selectedType);

                if (type == null)
                {
                    show = false;
                }
                else
                {
                    show = asset.GetType() == type || asset.GetType().IsSubclassOf(type);
                }
            }

            if (show)
            {
                tree.Add($"{asset.GetType().Name.Split("TcgEngine.")[0]}/{asset.name}", asset);
            }

        }

        return tree;
    }

    protected override void OnBeginDrawEditors()
    {
        if (MenuTree == null) return;

        SirenixEditorGUI.BeginHorizontalToolbar(MenuTree.Config.SearchToolbarHeight);
        {

            if (SirenixEditorGUI.ToolbarButton(new GUIContent("Clone")))
            {
                var selected = MenuTree.Selection.SelectedValue;

                if (selected == null || !selected.GetType().IsSubclassOf(typeof(ScriptableObject))) return;

                Type selectedType = selected.GetType();

                var objToSelect = EditorUtils.Clone(selectedType, selected);

                if (objToSelect != null)
                {
                    TrySelectMenuItemWithObject(objToSelect);
                }
            }
        }
        SirenixEditorGUI.EndHorizontalToolbar();
    }
}

public class CardEditor : OdinMenuEditorWindow
{
    [MenuItem("Tools/Moratelli/Card Manager")]
    private static void OpenEditor() => GetWindow<CardEditor>();

    public static bool ShowNotNull(UnityEngine.Object value)
    {
        return value != null;
    }

    public static void CloneAction<T>(object root, InspectorProperty property, T value)
    {
        if (value == null) return;

        string fieldName;
        if (property.Parent.Name.Contains("#") || property.Parent.Name.Contains("$"))
        {
            fieldName = property.Name;
        }
        else
        {
            fieldName = property.Parent.Name;
        }

        var field = root.GetType().GetField(fieldName);
        if (field != null && field.FieldType.IsArray)
        {
            var array = field.GetValue(root) as T[];
            T item = (T)EditorUtils.Clone(value.GetType(), value);

            if (item != null)
            {
                Array.Resize(ref array, array.Length + 1);
                array[^1] = item;
                field.SetValue(root, array);
            }
        }
        else if (field != null)
        {
            T item = (T)EditorUtils.Clone(value.GetType(), value);

            if (item != null)
            {
                field.SetValue(root, item);

            }
        }
    }

    static public void CreateNewAction(object root, InspectorProperty property)
    {
        string fieldName;

        if (property.Parent.Name.Contains("#") || property.Parent.Name.Contains("$"))
        {
            fieldName = property.Name;
        }
        else
        {
            fieldName = property.Parent.Name;
        }

        FieldInfo field = root.GetType().GetField(fieldName);

        if (field != null && field.FieldType.IsArray)
        {
            var array = field.GetValue(root) as object[];
            object item = EditorUtils.Clone(field.FieldType.GetElementType(), null, false);

            if (item != null)
            {
                Array.Resize(ref array, array.Length + 1);
                array[^1] = item;
                field.SetValue(root, array);
            }
        }
        else if (field != null)
        {
            object item = EditorUtils.Clone(field.FieldType, null, false);

            if (item != null)
            {
                field.SetValue(root, item);

            }
        }
    }

    public static void CreateNewActionButton(object root, InspectorProperty property)
    {
        if (SirenixEditorGUI.ToolbarButton(SdfIconType.PlusCircleDotted))
        {
            CreateNewAction(root, property);
        }
    }

    protected override OdinMenuTree BuildMenuTree()
    {
        var tree = new OdinMenuTree();
        tree.Config.DrawSearchToolbar = true;

        tree.AddAllAssetsAtPath("Cards", "Assets/TcgEngine/Resources/Cards", typeof(CardData), true, true).SortMenuItemsByName();
        tree.AddAllAssetsAtPath("Abilities", "Assets/TcgEngine/Resources/Abilities", typeof(AbilityData), true, true).SortMenuItemsByName();
        tree.AddAllAssetsAtPath("Effects", "Assets/TcgEngine/Resources/Effects", typeof(EffectData), true, true).SortMenuItemsByName();
        tree.AddAllAssetsAtPath("Conditions", "Assets/TcgEngine/Resources/Conditions", typeof(ConditionData), true, true).SortMenuItemsByName();
        tree.AddAllAssetsAtPath("Filters", "Assets/TcgEngine/Resources/Conditions", typeof(FilterData), true, true).SortMenuItemsByName();
        tree.AddAllAssetsAtPath("Status", "Assets/TcgEngine/Resources/Status", typeof(StatusData), true, true).SortMenuItemsByName();

        return tree;
    }

    protected override void OnBeginDrawEditors()
    {
        if (MenuTree == null) return;

        SirenixEditorGUI.BeginHorizontalToolbar(MenuTree.Config.SearchToolbarHeight);
        {

            GUILayout.Label("Card Editor");

            if (SirenixEditorGUI.ToolbarButton(new GUIContent("Clone")))
            {
                var selected = MenuTree.Selection.SelectedValue as UnityEngine.Object;

                if (selected == null) return;

                Type selectedType = selected.GetType();

                var objToSelect = EditorUtils.Clone(selectedType, selected);

                if (objToSelect != null)
                {
                    TrySelectMenuItemWithObject(objToSelect);
                }
            }
        }
        SirenixEditorGUI.EndHorizontalToolbar();
    }
}


public class CardDataProcessor : OdinAttributeProcessor<CardData>
{
    public override void ProcessChildMemberAttributes(InspectorProperty parentProperty, MemberInfo member, List<Attribute> attributes)
    {
        base.ProcessChildMemberAttributes(parentProperty, member, attributes);

        if (member.Name == "abilities" || member.Name == "traits")
        {
            var attribute = attributes.GetAttribute<ListDrawerSettingsAttribute>();

            attribute.OnTitleBarGUI = "@CardEditor.CreateNewActionButton($root, $property)";
        }
    }
}

public class AbilityDataProcessor : OdinAttributeProcessor<AbilityData>
{
    public override void ProcessChildMemberAttributes(InspectorProperty parentProperty, MemberInfo member, List<Attribute> attributes)
    {
        base.ProcessChildMemberAttributes(parentProperty, member, attributes);

        if (member.Name == "conditions_trigger" || member.Name == "conditions_target"
            || member.Name == "filters_target" || member.Name == "effects"
            || member.Name == "status" || member.Name == "chain_abilities")
        {
            var attribute = attributes.GetAttribute<ListDrawerSettingsAttribute>();

            attribute.OnTitleBarGUI = "@CardEditor.CreateNewActionButton($root, $property)";
        }

    }
}

public class EditorUtils
{
    private static void UpdateForType<T>(Type type, T source, T destination)
    {
        FieldInfo[] myObjectFields = type.GetFields(
            BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);

        foreach (FieldInfo fi in myObjectFields)
        {
            if (fi.FieldType.IsArray)
            {
                fi.SetValue(destination, (fi.GetValue(source) as Array).Clone());
            } else
            {
                fi.SetValue(destination, fi.GetValue(source));
            }  
        }
    }

    public static string GetFolder(Type selectedType)
    {
        string SOData = selectedType.ToString();

        if (selectedType.IsSubclassOf(typeof(ConditionData)))
        {
            SOData = "TcgEngine.ConditionData";
        }
        else if (selectedType.IsSubclassOf(typeof(FilterData)))
        {
            SOData = "TcgEngine.FilterData";
        }
        else if (selectedType.IsSubclassOf(typeof(EffectData)))
        {
            SOData = "TcgEngine.EffectData";
        }

        var folders = new Dictionary<string, string>
        {
            { "TcgEngine.CardData", "Cards" },
            { "TcgEngine.AbilityData", "Abilities" },
            { "TcgEngine.EffectData", "Effects" },
            { "TcgEngine.ConditionData", "Conditions" },
            { "TcgEngine.FilterData", "Filters" },
            { "TcgEngine.StatusData", "Status" },
            { "TcgEngine.TraitData", "Traits" },
            { "TcgEngine.TeamData", "Teams" },
            { "TcgEngine.RarityData", "Rarities" },
        };

        return folders.ContainsKey(SOData) ? $"Assets/TcgEngine/Resources/{folders[SOData]}" : "Assets/TcgEngine/Resources/";
    }


    public static T Clone<T>(Type selectedType, object selected, bool copyValues = true) where T : ScriptableObject
    {
        T clone = null;
        string folder = "";
        if (selected == null)
        {
            folder = GetFolder(selectedType);
        }
        else
        {
            string[] parts = AssetDatabase.GetAssetPath(selected as UnityEngine.Object).Split("/");
            Array.Resize(ref parts, parts.Length - 1);

            folder = string.Join("/", parts);
        }

        MethodInfo showDialogMethod = typeof(ScriptableObjectCreator).GetMethod("ShowDialog");
        MethodInfo genericShowDialogMethod = showDialogMethod.MakeGenericMethod(selectedType);
        object[] parameters = { $"{folder}", new Action<T>(obj =>
        {
            if (copyValues)
                UpdateForType(selectedType, selected as T, obj);

            var propertyInfo = obj.GetType().GetField("id");

            if (propertyInfo != null)
            {
                propertyInfo.SetValue(obj, obj.name.ToLower().Replace(" ", "_"));
            }

            clone = obj;
        })};

        genericShowDialogMethod.Invoke(null, parameters);

        return clone;
    }


    public static object Clone(Type selectedType, object selected, bool copyValues = true)
    {
        return Clone<ScriptableObject>(selectedType, selected, copyValues);
    }
}

2.2 Result

Now there is a button (+) to create SO and connect it in just 1 click, making easier to create new Abilities/Effects/Teams/Rarities/Conditions/Filters and Status.
image

3 CardDrawer.cs (Create this file inside some Editor folder)

This will improve how cards are showned in DeckData and effects like Sumon

3.1 Script

using Sirenix.OdinInspector.Editor;
using Sirenix.Utilities;
using Sirenix.Utilities.Editor;
using System.Collections;
using System.Collections.Generic;
using TcgEngine;
using UnityEditor;
using UnityEngine;

public class CardDrawer<T> : OdinValueDrawer<T> where T: CardData
{
    protected override void DrawPropertyLayout(GUIContent label)
    {
        var rect = EditorGUILayout.GetControlRect(label != null, 100);

        if (label != null)
        {
            rect.xMin = EditorGUI.PrefixLabel(rect.AlignCenterY(15), label).xMin;
        }
        else
        {
            rect = EditorGUI.IndentedRect(rect);
        }

        CardData card = this.ValueEntry.SmartValue;
        Texture texture = null;

        if (card)
        {
            texture = GUIHelper.GetAssetThumbnail(card.art_full, typeof(CardData), true);
            GUI.Label(rect.AddXMin(120).AlignMiddle(16), EditorGUI.showMixedValue ? "-" : card.id);
        }

        this.ValueEntry.WeakSmartValue = SirenixEditorFields.UnityPreviewObjectField(rect.AlignLeft(100), card, texture, this.ValueEntry.BaseValueType);
    }
}

3.2 Result


image

image

image

image

3.3 Result Card Finder

Helps you to find all Cards that have a specific Ability, Name and/or AbilityTrigger.
image

3.4 Scriptable Object Finder

Helps you to find all Scriptable Object Instances from a specific SO Class. Ex.: Find all instances of ConditionCardPile
image

3.5 Duplicate Finder

Helps you to find all duplicates. Ex.: Cards with same ids
image

image

Notes:

  • You MUST have TCG Engine to add the changes showned above;
  • You MUST have Odin to use the attributes showned above (with demo instealled);
  • Some changes are not listed here because are specific for my game;
  • Some classes could not exist in your project that extend from Filter/Condition and Effect, just remove it.
  • This gist was created using TCG Engine 1.09 and Odin 3.2.1.0, so is possible that in the future you will need to change minor details to make it work.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment