ちゃこブログ

お絵かきと UnityとBlender と日記

【Unity 制作日記、 C # 】いつも悩むよ C #

少しでもかっこよくコードを書きたいと思うとすぐに悩んでしまいます。
今回、久々に作成した Editor ツールの中で躓いてしまったところをメモしてみようと思いました。
charcotte.hateblo.jp

継承をするクラス構造

GUIStyleAndTextureViewer は、 Unity 既存の GUIStyleとTexture の名前を保存・ GUI をプレビュー・クリップボードにコピーできるツールとして制作を試みました。
最初はあまり時間をかけずに制作をしようと考えていたのでクラス設計も簡素なものでした。
このツールにおいて保存すべきデータは文字列とお気に入りだけなので、 stringとbool のみを持つ簡素なクラスを作成しました。 Window のタブで処理を分岐して GUI の描画処理を実装しました。

f:id:charcotte:20180814003952p:plain

アイテムのデータは ScriptableobjectにList を作って保存するようにしています。

f:id:charcotte:20180814125729p:plain

一旦完成したものの描画の際に毎回 new GUIStyle () したり EditorGUIUtility. Load () したりするのは好ましくないです。 Initialize 処理等でそれぞれをキャッシュして描画するという手がありましたが、なんだかスマートさに欠けている気がしました。
また、 UI も利便性を高めたかったので TreeView を利用することを決め、アイテムクラスを変更することにしました。

失敗

f:id:charcotte:20180814125740p:plain


アイテムのジェネリック型基底クラスを作り、 GUIStyleとTexture で派生させるように作ってみました。各要素を利用する前に一度 Initialize () を呼ぶことでスマートに GUI の実装が出来るようになりました。また、 Unity 公式サンプルの TreeView のクラスを利用するために、基底クラスに TreeElement クラスを継承させます。

TreeView サンプル
https://forum.unity.com/threads/new-treeview-api.447169/

f:id:charcotte:20180814170919p:plain

ところが、このような設計にしていると TreeView を実装するときにコードを書く量が増えてしまうことになってしまいました。 TreeViewWithTreeModel< GUIElement < GUIStyle >> とTreeViewWithTreeModel< GUIElement < Texture2D >> の二つのクラスを実装する必要があるからです。せっかくスマートに実装したかったのにこれでは本末転倒な気がしてきました。
基底クラスが非ジェネリック型クラスでないと、柔軟に利用しづらい面もあるようです。

最終的なクラス構造

TreeViewWithTreeModel を二つも書きたくなかったので、以下のように利用する基底クラスと派生させたいクラスの間にジェネリック型クラスを挟むということをしてみました。

f:id:charcotte:20180814171009p:plain
f:id:charcotte:20180814220130p:plain

以上の変更を加えたことにより、 TreeViewWIthTreeModel < GUIElement >のみを実装すれば良くなりました。 ScriptableObject に保存した派生クラスを基底クラスにキャストすることで TreeView のクラス内で GUI を実装しやすくなりました。 TreeView の詳しい実装方法など別記事で紹介出来たらします。
クラスの構造を深く考えることは細かい実装をする際に大きな助けになることを深く実感する今日この頃でした !

(-.-) < いやいやクラス構造を考えすぎていつもプログラム書くの遅いじゃーーん

List <派生クラス>から List <基底クラス>へのキャスト

クラス構造のところで、派生クラスから基底クラスへキャストする、とありましたが正確には ScriptableObject に保存している List <基底クラス>から List <派生クラス>へのキャストです。
私の頭だと一見以下のようなキャストができてしまうような気がしました。

List<ParentClass> ParentList = (List<ChildClass>)ChildList;

上記のキャストは不可能です。
できないならば、 for 文などで派生クラスから基底クラスにキャストしながら新しい List を作成するという方法が取れます。しかしこの方法はなんか負けた気分になったので他の方法をググってみました。
Linq を利用するとよりかっこよく List の要素ををキャストすることが出来ました。

ConvertAll
List (T).ConvertAll (TOutput) メソッド (Converter (T, TOutput)) (System. Collections. Generic)

List<ParentClass> ParentList = ChildList.ConvertAll<ParentClass>( e => e );

for 文でキャスト処理を書かなくても一行で記述できるのはスマートですね。

List <派生クラス>から List <基底クラス>へキャストすると参照はどうなるか

ScriptableObject は派生クラスで保存していますが、 TreeView では基底クラスにキャストされたデータを利用して GUI を表示します。ちゃんと ScriptableObject で保存されてるデータへ参照されるのかイメージが掴めなかったが、 GUI で値を操作すると ScirptableObject の方もちゃんと変更されていました。
ところが一つ問題が発生しました。 TreeView は、正確には TreeElement が継承されたクラスの List を利用して GUI を表示します。要素の一つをドラッグすることで順番を入れえることが可能で、順番を変更すると ListのIndex も更新されるようになっています。 List <派生クラス>から List <基底クラス>へキャストする際、派生クラスへのデータの更新は可能でしたが、 ListのIndex は更新されなかったのです。つまり、 List への参照は無くなってしまったのです。 ( そういう事なんでしょうか・・ ?)List の参照を考えたことがあまりなかったので勉強になりました。
これをどう解決したかというと、 TreeView 側の ListのIndex が更新されたら ScriptableObjectのList へデータを再度派生クラスへキャストし、データを上書きするという方法を取りました。非常にスマートさに欠けてしまいました。

まとめ

継承、キャスト、参照について学ぶことが多かったです。
これらは C # やってるなら当たり前のように理解していなきゃいけない部分ですが、実際に手を動かすときについ悩んでしまうことが多いので早く C # に馴染んでいきたいです。