質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

配列

配列は、各データの要素(値または変数)が連続的に並べられたデータ構造です。各配列は添え字(INDEX)で識別されています。

Q&A

解決済

1回答

3121閲覧

Unity C# Listをエディタ拡張で2次元配列を扱いたい

suvera

総合スコア106

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

配列

配列は、各データの要素(値または変数)が連続的に並べられたデータ構造です。各配列は添え字(INDEX)で識別されています。

0グッド

0クリップ

投稿2017/10/22 12:59

編集2017/10/23 23:10

###前提・実現したいこと
エディタ拡張で2次元配列を扱って
inspector(EditorWindow)で色々値を変更し
実行時にその値が反映されるようにしたいです。

###発生している問題・エラーメッセージ
inspector上では成功しているように見えます。
具体的にはサイズを増やせば欄縦にも横にも増えて
値の変更なども維持されます。

しかし、実行ボタンを押すと初期化されるのか何故かNullに変わってしまいます。
当然inspectorもNullに変わります。

###該当のソースコード

# スクリプトの一部 MonoBehaviour継承クラス ```MapManagerClass public List<List<int>> map; void Start() { Debug.Log(map); } public void SetMap(List<List<BlockStatus>> newMap) { map = newMap; }

Editorスクリプト

void OnGUI() { mapManager = EditorGUILayout.ObjectField("MapManagerコンポーネント を持ったオブジェクト", mapManager, typeof(MapManager), true) as MapManager; if (mapManager == null) return; List<List<int>> map = mapManager.map; int mapSize = 0; Debug.Log(map); if (map != null) { mapSize = map.Count; } else { mapManager.map = new List<List<int>>(); map = mapManager.map; } foldoutMapList = EditorGUILayout.Foldout(foldoutMapList, "map"); if (this.foldoutMapList) { mapSize = EditorGUILayout.IntField("Size", mapSize); mapSize = Mathf.Clamp(mapSize, 1, 5); EditorGUILayout.BeginHorizontal(GUI.skin.box); { for (int index = 0; index < mapSize; index++) { EditorGUILayout.BeginVertical(GUI.skin.box); { if (index < map.Count) { EditorGUILayout.LabelField(index.ToString()); } else { map.Add(new List<int>()); } EditorGUILayout.EndVertical(); } } EditorGUILayout.EndHorizontal(); if (mapSize < map.Count) { map.RemoveRange(mapSize, map.Count - mapSize); } } mapManager.SetMap(map); }
###試したこと 1次元で同じような処理をやってみました。 こちらは思ったように動作して、実行時にNullになるようなことはありませんでした。 ###補足情報(言語/FW/ツール等のバージョンなど) Unity2017 C#

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

Serialize Nested Lists - Unity Answersによると、どうやらUnityは入れ子のListをシリアライズしてくれないようですね...
回答者の方は、下記のようにラッパークラスを作る方法を提案されていますが、この形に書き換えてみるとどうでしょうか?


absameen による 質問 · 2012年07月24日 17:29

Serialize Nested Lists

Will Unity not serialize nested lists such as:

List<List<int>> nList = new List<List<int>>();

How can we serialize this list?


CapnCromulent による 回答 · 2012年09月14日 04:16

Unfortunately, you'll have to write a wrapper class, like:

[System.Serializable] public class ListWrapper { public List<int> myList; }

then declare within your ScriptableObject

List<ListWrapper> nList =new List<ListWrapper>();

[追記]
多次元のListをInspectorに表示する【Unity】 - (:3[kanのメモ帳]で、同様にラッパー方式で多次元Listをインスペクタ上でも取り扱えるようにする方法が紹介されていました。こちらもご参考になりそうです。

[コメントを受けて追記]
いまいちしっくりいく回答を提示できず申し訳ないです...
下記のような感じでインデクサーを追加してやれば多少はスクリプトの見た目がすっきりしますかね?あんまり大差はないですが...

C#

1using System; 2using System.Collections.Generic; 3using UnityEngine; 4 5public class List2DTest : MonoBehaviour 6{ 7 public List<ListInt> Test; 8 9 private void Start() 10 { 11 Debug.Log(this.Test[0][0]); 12 } 13} 14 15[Serializable] 16public class ListInt 17{ 18 [SerializeField] 19 private List<int> storage = new List<int>(); 20 21 public int this[int i] 22 { 23 get 24 { 25 return this.storage[i]; 26 } 27 28 set 29 { 30 this.storage[i] = value; 31 } 32 } 33}

[改善案を思いついたので追記]
ISerializationCallbackReceiverを実装することでシリアライズ時の挙動をコントロールできるらしいので、今さらながら試してみました。

まず、MapManagerの方の基本戦略としては、シリアライズして保存しておくためのラッパークラス方式のデータ(コード中のserializableMap)と、他のスクリプトから使うときのためのList<List<int>>(コード中のmap)に二重化しておいて、シリアライズ・デシリアライズ時に一方を他方に変換して同期することにしました。データ量が増えてしまいますが、一応それっぽく動くのではないでしょうか(同期部分は手抜きして毎回新しいオブジェクトを再生成していますが、既存のオブジェクトを再利用する形にすればいくらか効率的だと思います)。

C#

1using System; 2using System.Collections.Generic; 3using System.Linq; 4using UnityEngine; 5 6public class MapManager : MonoBehaviour, ISerializationCallbackReceiver 7{ 8 [NonSerialized] 9 public List<List<int>> map; 10 11 [SerializeField, HideInInspector] 12 private List<ListInt> serializableMap; 13 14 void Start() { 15 Debug.Log(map); 16 } 17 18 public void SetMap(List<List<int>> newMap) { 19 map = newMap; 20 } 21 22 public void OnBeforeSerialize() 23 { 24 this.serializableMap = this.map == null ? null : this.map.Select(subList => new ListInt(subList)).ToList(); 25 } 26 27 public void OnAfterDeserialize() 28 { 29 this.map = this.serializableMap == null ? null : this.serializableMap.Select(listInt => listInt.data).ToList(); 30 } 31} 32 33[Serializable] 34public class ListInt 35{ 36 public List<int> data; 37 38 public ListInt(List<int> list) 39 { 40 this.data = list ?? new List<int>(); 41 } 42}

エディターウィンドウの方ですが、プレイモードにすると操作対象のMapManagerの参照が失われるので、代わりにMapManagerを持っているゲームオブジェクトへの参照を保持させることにしました。
それ以外は、2次元表示を試すためにIntFieldを入れてみたりしましたが、mapへのアクセス自体はご質問者さんご提示のコードに倣っています。

C#

1using System.Collections.Generic; 2using UnityEditor; 3using UnityEngine; 4 5public class MapManagerEditorWindow : EditorWindow 6{ 7 private const float MinWidth = 16.0f; 8 private const float MaxWidth = 1024.0f; 9 10 private GameObject mapManagerObject; 11 private bool foldoutMapList; 12 13 [MenuItem("Window/Map Editor")] 14 static void Open() { 15 EditorWindow.GetWindow<MapManagerEditorWindow>("Map Editor"); 16 } 17 18 void OnGUI() { 19 MapManager mapManager = EditorGUILayout.ObjectField("MapManagerコンポーネント を持ったオブジェクト", mapManagerObject == null ? null : mapManagerObject.GetComponent<MapManager>(), typeof(MapManager), true) as MapManager; 20 if (mapManager == null) 21 return; 22 mapManagerObject = mapManager.gameObject; 23 List<List<int>> map = mapManager.map; 24 int mapSize = 0; 25 Debug.Log(map); 26 if (map != null) { 27 mapSize = map.Count; 28 } else { 29 mapManager.map = new List<List<int>>(); 30 map = mapManager.map; 31 } 32 foldoutMapList = EditorGUILayout.Foldout(foldoutMapList, "map"); 33 if (this.foldoutMapList) { 34 mapSize = EditorGUILayout.IntField("Size", mapSize); 35 mapSize = Mathf.Clamp(mapSize, 1, 5); 36 EditorGUILayout.BeginHorizontal(GUI.skin.box); 37 { 38 for (int index = 0; index < mapSize; index++) { 39 EditorGUILayout.BeginVertical(GUI.skin.box); 40 { 41 if (index < map.Count) { 42 EditorGUILayout.LabelField(index.ToString(), GUILayout.MinWidth(MinWidth), GUILayout.MaxWidth(MaxWidth)); 43 } else { 44 map.Add(new List<int>()); 45 } 46 List<int> column = map[index]; 47 for (int rowIndex = 0; rowIndex < mapSize; rowIndex++) { 48 if (rowIndex >= column.Count) { 49 column.Add(0); 50 } 51 column[rowIndex] = EditorGUILayout.IntField(column[rowIndex], GUILayout.MinWidth(MinWidth), GUILayout.MaxWidth(MaxWidth)); 52 } 53 EditorGUILayout.EndVertical(); 54 if (mapSize < column.Count) { 55 column.RemoveRange(mapSize, column.Count - mapSize); 56 } 57 } 58 } 59 EditorGUILayout.EndHorizontal(); 60 if (mapSize < map.Count) { 61 map.RemoveRange(mapSize, map.Count - mapSize); 62 } 63 } 64 mapManager.SetMap(map); 65 } 66 } 67}

投稿2017/10/24 04:33

編集2017/10/27 23:49
Bongo

総合スコア10807

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

suvera

2017/10/24 10:37

ありがとうございます。 自分も追記のところのサイトに行きつきましてその方法で解決しました。 まぁ本当はList<List<int>>の形がきれいだとは思うんですが無理なようですしList<Class>で行こうと思います。 なお、Nullになるのはinspectorに表示して扱っていなかったため実行時に初期化されていたからでした。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問