Last active
June 14, 2023 20:35
-
-
Save JonahGrimm/accc0f3c8ca60456bde745a99b950d78 to your computer and use it in GitHub Desktop.
The following is a basic example of Polymorphic Serialization in Unity. It uses Food as the concept for a base class and the derived types are different types of foods. It is complete with a property drawer so that it is easy to work with. Be sure to remove the $ from the first file's name. That's just for sorting.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Collections; | |
using System.Collections.Generic; | |
using UnityEngine; | |
public class PolymorphicSerializationExample : MonoBehaviour | |
{ | |
[SerializeReference] public List<Food> classes = new List<Food>(); | |
#if UNITY_EDITOR | |
private void OnValidate() | |
{ | |
for (int i = 0; i < classes.Count; i++) | |
{ | |
if (classes[i] == null) | |
{ | |
classes[i] = new AppleFood(); | |
UnityEditor.EditorUtility.SetDirty(this); | |
} | |
} | |
} | |
#endif | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using UnityEngine; | |
[Serializable] | |
public abstract class Food | |
{ | |
public enum FoodType | |
{ | |
Apple, | |
Orange, | |
Grape | |
} | |
public FoodType Type; | |
} | |
public class AppleFood : Food | |
{ | |
public int AppleCount; | |
public AppleFood() | |
{ | |
Type = FoodType.Apple; | |
} | |
} | |
public class OrangeFood : Food | |
{ | |
public float OrangeSize; | |
public OrangeFood() | |
{ | |
Type = FoodType.Orange; | |
} | |
} | |
public class GrapeFood : Food | |
{ | |
public Color GrapeColor; | |
public GrapeFood() | |
{ | |
Type = FoodType.Grape; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Collections; | |
using System.Collections.Generic; | |
using System; | |
using UnityEngine; | |
using UnityEditor; | |
using System.Linq; | |
using System.Text.RegularExpressions; | |
[CustomPropertyDrawer(typeof(Food))] | |
public class FoodPropertyDrawer : PropertyDrawer | |
{ | |
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) | |
{ | |
EditorGUI.BeginProperty(position, label, property); | |
Rect systemRect = new Rect(position.x, position.y, position.width - 25, EditorGUIUtility.singleLineHeight); | |
PolymorphicSerializationExample script = (PolymorphicSerializationExample)property.serializedObject.targetObject; | |
int elementIndex = ExtractIndexFromLabel(label.text); | |
// Create drop down options for later | |
string[] names = System.Enum.GetNames(typeof(Food.FoodType)); | |
string[] options = new string[names.Length+1]; | |
names.CopyTo(options, 0); | |
options[options.Length-1] = "(X) DELETE"; | |
// Draw fields | |
int dropDownValue = -1; | |
int count = 0; | |
var enumerator = property.GetEnumerator(); | |
position.height = EditorGUIUtility.singleLineHeight - 1f; | |
while (enumerator.MoveNext()) | |
{ | |
var prop = enumerator.Current as SerializedProperty; | |
if (prop == null) continue; | |
// Skip the first item, since that's going to be the type. Ignore it. | |
if (count == 0) | |
{ | |
count++; | |
continue; | |
} | |
// If this is our first item, we want to draw our down arrow. | |
if (count == 1) | |
{ | |
Rect editRect = new Rect(position.x + position.width - 20, position.y, 20, position.height); | |
dropDownValue = EditorGUI.Popup(editRect, -1, options); | |
// Reduce width of our main field | |
position.width -= 25; | |
} | |
// Render property and prepare for next line | |
EditorGUI.PropertyField(position, prop); | |
position.y += EditorGUIUtility.singleLineHeight; | |
// If this is our first item, we need to readjust our width back to normal | |
if (count == 1) position.width += 25; | |
count++; | |
} | |
EditorGUI.EndProperty(); | |
// Respond to drop down change | |
if (dropDownValue != -1) | |
{ | |
if (dropDownValue == options.Length-1) | |
{ | |
// Delete option | |
script.classes.RemoveAt(elementIndex); | |
EditorUtility.SetDirty(script); | |
} | |
else | |
{ | |
string typeName = $"{(Food.FoodType)dropDownValue}Food, Assembly-CSharp"; | |
var type = Type.GetType(typeName); | |
script.classes[elementIndex] = (Food)Activator.CreateInstance(type); | |
} | |
EditorUtility.SetDirty(script); | |
} | |
} | |
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) | |
{ | |
int count = 0; | |
var enumerator = property.GetEnumerator(); | |
while (enumerator.MoveNext()) | |
{ | |
var prop = enumerator.Current as SerializedProperty; | |
if (prop == null) continue; | |
count++; | |
} | |
return Mathf.Max(count-1, 0) * EditorGUIUtility.singleLineHeight; | |
} | |
private int ExtractIndexFromLabel(string label) | |
{ | |
// Define the regular expression pattern | |
string pattern = @"Element (\d+)"; | |
// Create a Regex object with the pattern | |
Regex regex = new Regex(pattern); | |
// Match the pattern against the input string | |
Match match = regex.Match(label); | |
// Extract the captured number | |
if (match.Success) | |
{ | |
string numberString = match.Groups[1].Value; | |
// Parse the number string to an integer | |
int number = int.Parse(numberString); | |
return number; | |
} | |
// Return a default value if no match found | |
return 0; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment