課題
以下のようなクラスがあったとして
Person.cs
[System.Serializable]
public class Person
{
public string name;
public int age;
}
[System.Serializable]
public class Man : Person
{
public string manField;
}
[System.Serializable]
public class Woman : Person
{
public int womanField;
}
TestPersonListをJsonUtilityでシリアライズすると
Test1.cs
[System.Serializable]
public class TestPersonList{
public List<Person> personList = new List<Person>();
}
void TestPersonFailed () {
var target = new List<Person> () {
new Man { age = 18, name = "男性", manField = "man" },
new Woman { age = 16, name = "女性", womanField = 100 }
};
var testPersonList = new TestPersonList(){personList = target};
var json = JsonUtility.ToJson (testPersonList); // manField, womanFieldが消える
Debug.Log (json);
}
manField
、womanField
がJSONに含まれません。
これをJsonUtilityを使うことは維持しつつ解決したい。
対応
JsonUtilityでシリアライズできる形に整形する。
以下のようなスクリプトを作ることで解決できます。
PersonList.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[System.Serializable]
public class PersonList {
[SerializeField] List<int> manIndexList = new List<int> ();
[SerializeField] List<Man> manList = new List<Man> ();
[SerializeField] List<int> womanIndexList = new List<int> ();
[SerializeField] List<Woman> womanList = new List<Woman> ();
public PersonList (List<Person> src) {
for (int i = 0; i < src.Count; i++) {
switch (src[i]) {
case Man man:
manIndexList.Add (i);
manList.Add (man);
break;
case Woman woman:
womanIndexList.Add (i);
womanList.Add (woman);
break;
}
}
}
public List<Person> Convert () {
int length =
manIndexList.Count +
womanIndexList.Count;
var rtn = new Person[length];
for (int i = 0; i < manIndexList.Count; i++) {
rtn[manIndexList[i]] = manList[i];
}
for (int i = 0; i < womanIndexList.Count; i++) {
rtn[womanIndexList[i]] = womanList[i];
}
return new List<Person> (rtn);
}
public static List<System.Type> GetAllDerivedTypes () {
return new List<System.Type> () {
typeof (Man),
typeof (Woman)
};
}
}
JSON化、復元ともにできました。
Test2.cs
void TestPersonListOK () {
var target = new List<Person> () {
new Man { age = 18, name = "男性", manField = "man" },
new Woman { age = 16, name = "女性", womanField = 100 }
};
var personList = new PersonList (target);
// Object -> string
var json = JsonUtility.ToJson (personList);
Debug.Log (json);
// string -> Object
var personList2 = JsonUtility.FromJson<PersonList> (json);
var target2 = personList2.Convert ();
var man = target2[0] as Man;
Debug.Log ($"age={man.age},name={man.name}, manField={man.manField}");
var woman = target2[1] as Woman;
Debug.Log ($"age={woman.age},name={woman.name}, manField={woman.womanField}");
}
問題点
PersonList作るの面倒すぎ
対応2
PersonListを自動生成する。
DerivedClassListGenerator.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
public class DerivedClassListGenerator : CodeGenerator {
public void CreateAt (Type baseType) {
var derivedTypes = GetAllDerivedTypes (AppDomain.CurrentDomain, baseType);
if (derivedTypes.Count == 0) {
Debug.LogError ($"No Derived Class : {baseType.Name}");
return;
}
// テンプレートをロード
var templateLoader = new TemplateLoader ();
var writer = new Writer ();
var template = templateLoader.LoadTemplate (TemplatesDir + "DerivedClassList.txt");
// 各項目の文言を生成
string className = baseType.Name + "List";
string fieldContent = GetField(derivedTypes);
string switchContent = GetSwitch(derivedTypes);
string convertContent = GetConvert(derivedTypes, baseType);
string getAllDerivedTypesContent = GetGetAllDerivedTypes(derivedTypes);
var content = template.text;
content = content.Replace("$ClassName$", className);
content = content.Replace("$Field$", fieldContent);
content = content.Replace("$DataName$", baseType.Name);
content = content.Replace("$Switch$", switchContent);
content = content.Replace("$Convert$", convertContent);
content = content.Replace("$GetAllDerivedTypes$", getAllDerivedTypesContent);
// スクリプト生成
writer.CreateNewScriptAt(className, content);
}
string GetField (List<Type> derivedTypes) {
var stringBuilder = new StringBuilder ();
bool isFirst = true;
foreach (var type in derivedTypes) {
string className = type.Name;
string camelClassName = UpperCamelToCamel (className);
string prefix = isFirst ? "" : Tabs(1);
stringBuilder.AppendLine (prefix + $"[SerializeField] List<int> {camelClassName}IndexList = new List<int>();");
stringBuilder.AppendLine (Tabs(1) + $"[SerializeField] List<{className}> {camelClassName}List = new List<{className}>();");
isFirst = false;
}
return stringBuilder.ToString ();
}
string GetSwitch (List<Type> derivedTypes) {
var stringBuilder = new StringBuilder ();
bool isFirst = true;
foreach (var type in derivedTypes) {
string className = type.Name;
string camelClassName = UpperCamelToCamel (className);
string prefix = isFirst ? "" : Tabs(4);
stringBuilder.AppendLine (prefix + $"case {className} {camelClassName}:");
stringBuilder.AppendLine (Tabs(5) + $"{camelClassName}IndexList.Add(i);");
stringBuilder.AppendLine (Tabs(5) + $"{camelClassName}List.Add({camelClassName});");
stringBuilder.AppendLine (Tabs(5) + "break;");
isFirst = false;
}
return stringBuilder.ToString ();
}
string GetConvert(List<Type> derivedTypes, Type baseType){
var stringBuilder = new StringBuilder ();
stringBuilder.AppendLine("int length = ");
for (int i = 0; i < derivedTypes.Count; i++){
string className = derivedTypes[i].Name;
string camelClassName = UpperCamelToCamel (className);
if(i == derivedTypes.Count -1 ){
stringBuilder.AppendLine (Tabs(3) + $"{camelClassName}IndexList.Count;");
}else{
stringBuilder.AppendLine (Tabs(3) + $"{camelClassName}IndexList.Count +");
}
}
stringBuilder.AppendLine(Tabs(2) + $"var rtn = new {baseType.Name}[length];");
for (int i = 0; i < derivedTypes.Count; i++){
string className = derivedTypes[i].Name;
string camelClassName = UpperCamelToCamel (className);
stringBuilder.AppendLine (Tabs(2) + $"for (int i = 0; i < {camelClassName}IndexList.Count; i++)");
stringBuilder.AppendLine (Tabs(2) + "{");
stringBuilder.AppendLine (Tabs(3) + $"rtn[{camelClassName}IndexList[i]] = {camelClassName}List[i];");
stringBuilder.AppendLine (Tabs(2) + "}");
}
stringBuilder.Append(Tabs(2) +$"return new List<{baseType.Name}>(rtn);");
return stringBuilder.ToString ();
}
string GetGetAllDerivedTypes(List<Type> derivedTypes){
var stringBuilder = new StringBuilder ();
for (int i = 0; i < derivedTypes.Count; i++){
string className = derivedTypes[i].Name;
string camelClassName = UpperCamelToCamel (className);
if(i == derivedTypes.Count -1 ){
stringBuilder.Append ( Tabs(3) + $"typeof({className})");
}else{
string prefix = i == 0 ? "" : Tabs(3);
stringBuilder.AppendLine (prefix + $"typeof({className}),");
}
}
return stringBuilder.ToString ();
}
public List<System.Type> GetAllDerivedTypes (System.AppDomain appDomain, System.Type baseType) {
var result = new List<System.Type> ();
var assemblies = appDomain.GetAssemblies ();
for (int i = 0; i < assemblies.Length; i++) {
var types = assemblies[i].GetTypes ();
for (int j = 0; j < types.Length; j++) {
if (types[j].IsSubclassOf (baseType)) {
result.Add (types[j]);
}
}
}
return result;
}
}
CodeGenerator.cs
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using UnityEditor;
using UnityEngine;
public class CodeGenerator {
[System.Serializable]
public class Field {
public string name;
public string fieldType;
public string GetText (bool isPublic = true) {
string format = isPublic ? "public {0} {1};" : "{0} {1};";
return string.Format (format, fieldType, name);
}
}
public static readonly string TemplatesDir = "Assets/Scripts/Editor/CodeGenerator/Templates/";
public class TemplateLoader {
public TextAsset LoadTemplate (string path) {
return AssetDatabase.LoadAssetAtPath<TextAsset> (path);
}
}
public class Writer {
public void CreateNewScript (string path, string content) {
using (StreamWriter streamWriter = File.CreateText (path + ".cs")) {
streamWriter.WriteLine (content);
}
AssetDatabase.Refresh ();
}
public void CreateNewScriptAt (string fileName, string content) {
var path = EditorUtility.OpenFolderPanel ("Select Create Folder", "", "");
CreateNewScript (System.IO.Path.Combine (path, fileName), content);
}
}
public string Tabs (int level) {
string rtn = "";
for (int i = 0; i < level; i++) {
rtn += "\t";
}
return rtn;
}
public string UpperCamelToCamel (string src) {
if (string.IsNullOrEmpty (src)) return src;
return char.ToLowerInvariant (src[0]) + src.Substring (1, src.Length - 1);
}
}
DerivedClassList.txt
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[System.Serializable]
public class $ClassName$
{
$Field$
public $ClassName$(List<$DataName$> src){
for (int i = 0; i < src.Count; i++)
{
switch (src[i])
{
$Switch$
}
}
}
public List<$DataName$> Convert(){
$Convert$
}
public static List<System.Type> GetAllDerivedTypes(){
return new List<System.Type>(){
$GetAllDerivedTypes$
};
}
}
まとめ
FullSerializerを使わずにJsonUtilityでがんばろうという試み
ゴリ押しですがいちおう達成はできました。