Сериализация данных в Unity
У меня есть список класса "MenuItem":
[SerializeField] protected List<ChoiceStructures.ButtonItem> menuItems;
от него наследуются еще 3 класса:
public enum ButtonState { common, characteristic, serious, relations, none }
[Serializable]
public class ButtonItem
{
public int index;
public string optionText;
public Block targetBlock;
public ButtonState buttonState;
public bool premiumButton = false;
public int premiumCurrency;
public virtual bool CheckOpportunity()
{
if (!premiumButton || premiumCurrency == 0)
return true;
return Communicator.TryMakePurchase(premiumCurrency);
}
public virtual void DrawGUI(ref List<ButtonItem> menuItems, in int index, Action middleSrc)
{
ButtonItem item = this;
item.index = index;
EditorGUILayout.BeginHorizontal(); GUILayout.FlexibleSpace();
EditorGUILayout.LabelField($"Menu {index}");
if (GUILayout.Button("-", GUILayout.MaxWidth(17)))
{
menuItems.Remove(item);
return;
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.LabelField($"Class: {item.GetType().Name}");
EditorGUILayout.Space(4);
item.optionText = EditorGUILayout.TextField("option text", item.optionText);
middleSrc();
EditorGUILayout.Space(2);
item.premiumButton = EditorGUILayout.Toggle("Is premium button", item.premiumButton);
if (item.premiumButton)
{
item.premiumCurrency = EditorGUILayout.IntField("Premium currency", item.premiumCurrency);
}
EditorGUILayout.Space(3);
}
}
[Serializable]
public class ButtonCharacteristic : ButtonItem
{
public MainHero.Characteristic characteristic;
public Operation operation;
public int value;
public override bool CheckOpportunity()
{
if (characteristic == null)
{
Debug.Log("Характеристика пустая!");
return false;
}
switch (operation)
{
case Operation.add:
characteristic.value += value;
break;
case Operation.remove:
characteristic.value -= value;
break;
default:
return false;
}
return base.CheckOpportunity();
}
public override void DrawGUI(ref List<ButtonItem> menuItems, in int i, Action middleSrc)
{
base.DrawGUI(ref menuItems, i, middleSrc);
characteristic = CommandEditorTools.CharacteristicField(characteristic,
new GUIContent("Target Characteristic", "Characteristic for change"),
new GUIContent("<None>"));
if (characteristic != null)
{
operation = (Operation)EditorGUILayout.EnumPopup("Operation", operation);
value = EditorGUILayout.IntField("Value", value);
}
}
}
[Serializable]
public class ButtonSerious : ButtonItem
{
public string key;
public bool WasChosen;
public override bool CheckOpportunity()
{
// Позже доделать
return base.CheckOpportunity();
}
public override void DrawGUI(ref List<ButtonItem> menuItems, in int i, Action middleSrc)
{
base.DrawGUI(ref menuItems, i, middleSrc);
key = EditorGUILayout.TextField("Key to save", key);
}
}
[Serializable]
public class ButtonRelation : ButtonItem
{
public MinorCharacter character;
public Operation operation;
public float value;
public override bool CheckOpportunity()
{
if (character == null)
{
Debug.Log("Персонажа нет!");
return false;
}
switch (operation)
{
case Operation.add:
character.relationPoints += value;
break;
case Operation.remove:
character.relationPoints -= value;
break;
default:
return false;
}
return base.CheckOpportunity();
}
public override void DrawGUI(ref List<ButtonItem> menuItems, in int i, Action middleSrc)
{
base.DrawGUI(ref menuItems, i, middleSrc);
character = CommandEditorTools.MinorCharacterField(character,
new GUIContent("Character", "Character change sprite"),
new GUIContent("<None>"),
CharacterCD.GetObjectsList
.Where(x => x is MinorCharacter)
.Select(x => x as MinorCharacter)
.ToList());
if (character != null)
{
operation = (Operation)EditorGUILayout.EnumPopup("Operation", operation);
value = EditorGUILayout.FloatField("Value", value);
}
}
}
Через инспектор я могу добавить все 4 класса в этот список (то есть родительский класс и классы наследники).
Скрипт кастомного инспектора:
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;
namespace Fungus.EditorUtils
{
[CustomEditor(typeof(Choices))]
public class ChoicesEditor : CommandEditor
{
protected SerializedProperty questionTextProp;
protected SerializedProperty decisionProp;
protected SerializedProperty characterProp;
protected SerializedProperty dialogWindowProp;
protected SerializedProperty renderPointProp;
protected SerializedProperty useCharacterSettingsProp;
protected SerializedProperty menuItemsProp;
public override void OnEnable()
{
base.OnEnable();
questionTextProp = serializedObject.FindProperty("questionText");
decisionProp = serializedObject.FindProperty("description");
characterProp = serializedObject.FindProperty("character");
dialogWindowProp = serializedObject.FindProperty("dialogWindow");
renderPointProp = serializedObject.FindProperty("renderPoint");
useCharacterSettingsProp = serializedObject.FindProperty("useCharacterSettings");
menuItemsProp = serializedObject.FindProperty("menuItems");
}
public override void DrawCommandGUI()
{
serializedObject.Update();
Choices choicesCD = target as Choices;
var flowchart = FlowchartWindow.GetFlowchart();
if (flowchart == null) return;
ChoiceStructures.ButtonState buttonState = ChoiceStructures.ButtonState.none;
EditorGUILayout.PropertyField(questionTextProp);
EditorGUILayout.PropertyField(decisionProp);
ObjectField<CharacterCD>(characterProp,
new GUIContent("Character", "Character that is speaking"),
new GUIContent("<None>"),
CharacterCD.GetObjectsList);
if (choicesCD.GetCharacter != null)
{
EditorGUILayout.PropertyField(useCharacterSettingsProp);
EditorGUILayout.BeginVertical(); EditorGUILayout.Space(5); EditorGUILayout.EndVertical();
}
else useCharacterSettingsProp.boolValue = false;
bool useCharacterSettings = useCharacterSettingsProp.boolValue;
EditorGUILayout.Space(5);
if (useCharacterSettings)
{
choicesCD.DialogWindow = choicesCD.GetCharacter.GetDialogWindow;
}
else
{
EditorGUILayout.PropertyField(dialogWindowProp);
if (choicesCD.GetCharacter != null)
{
ObjectField<CharacterRenderPoint>(renderPointProp,
new GUIContent("PointsToRenderCharacter"),
new GUIContent("<None>"),
CharacterRenderPoint.GetObjectsList);
}
}
EditorGUILayout.Space(20);
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("Add new menu item");
EditorGUILayout.Space(2);
buttonState = (ChoiceStructures.ButtonState)EditorGUILayout.EnumPopup(buttonState);
List<ChoiceStructures.ButtonItem> menuItems = choicesCD.GetMenuItems;
switch (buttonState)
{
case ChoiceStructures.ButtonState.common:
ChoiceStructures.ButtonItem buttonItem = new ChoiceStructures.ButtonItem
{
buttonState = buttonState
};
menuItems.Add(buttonItem);
break;
case ChoiceStructures.ButtonState.characteristic:
ChoiceStructures.ButtonItem buttonCharacteristic = new ChoiceStructures.ButtonCharacteristic
{
buttonState = buttonState
};
menuItems.Add(buttonCharacteristic);
break;
case ChoiceStructures.ButtonState.serious:
ChoiceStructures.ButtonItem buttonSerious = new ChoiceStructures.ButtonSerious
{
buttonState = buttonState
};
menuItems.Add(buttonSerious);
break;
case ChoiceStructures.ButtonState.relations:
ChoiceStructures.ButtonItem buttonRelation = new ChoiceStructures.ButtonRelation
{
buttonState = buttonState
};
menuItems.Add(buttonRelation);
break;
case ChoiceStructures.ButtonState.none:
break;
default:
break;
}
EditorGUILayout.EndHorizontal();
int index = 0;
foreach (var item in menuItems)
{
index++; EditorGUILayout.Space(10);
item.DrawGUI(ref menuItems, index, delegate
{
EditorGUILayout.BeginHorizontal();
item.targetBlock = CommandEditorTools.BlockField(item.targetBlock,
new GUIContent("Target Block", "Block to call when option is selected"),
new GUIContent("<None>"),
flowchart);
const int popupWidth = 17;
if (item.targetBlock == null && GUILayout.Button("+", GUILayout.MaxWidth(popupWidth)))
{
var fw = EditorWindow.GetWindow<FlowchartWindow>();
var t = (Choices)target;
var activeFlowchart = t.GetFlowchart();
var newBlock = fw.CreateBlockSuppressSelect(activeFlowchart, t.ParentBlock._NodeRect.position - Vector2.down * 60);
item.targetBlock = newBlock;
activeFlowchart.SelectedBlock = t.ParentBlock;
}
EditorGUILayout.EndHorizontal();
});
}
serializedObject.ApplyModifiedProperties();
}
}
}
И все это работает пока я нахожусь в эдиторе:
но если я запущу сцену, или выйду из unity, то все наследуемые классы от MenuItem в этом списке превратятся в этот же класс
Почему я думаю что проблемы в сериализации:
Я уже прочел много информации об этой проблеме, и решил проверить как unity сохраняет этот список в файле сцены (.unit)
и понял, что юнити просто не сохраняет классы наследники как надо, он их сохраняет как родительский класс.
Я пробовал добавлять ко всем классам атрибут [Serializable], но это не помогло
Я знаю множество решений этой проблемы, но они убивают всю красоту ООП. Так что я снова уточню, что мне нужно решить проблему с сериализацией unity, при этом оставив концепцию этих 4 классов.


