見出し画像

【unity】Arbor3のparameter containerをListに変換する【アドカレ夏2020】

※この記事は「Unity アセット真夏のアドベントカレンダー 2020 Summer!」25日目の記事になります。
前日の記事はyunodaさんの「Unityでシェーダーを作るなら、Amplify Shader Editorがおすすめ!」です。
翌日の記事は澪rumさんの「Magnetic Scroll Viewの紹介」です。

Arbor3とはなんぞや。

Arbor3、大好き〜!!

皆さん、コーディングはお好きですか?
Unityはゲーム制作の敷居を格段に下げた、ゲーム制作の民主化なんて揶揄されるゲームエンジンです。その為ゲームエンジニアのみならず、様々な出自の人がゲーム制作を志すことができるようになりました。

将来有望な学生から、日頃はゲームどころかITさえ無関係な職に付きながら余暇にゲームを制作する社会人まで。また、建築関係や映像関係、芸術家などゲームとはまた違う現場にも導入される例もあります。
そんなわけですから、中にはUnityに触りながらもコーディングに苦手意識がある人も少なくないと思います。

ご安心ください! わたしもそうでした!

でも……これからご案内するArbor3を使えば、コーディングの敷居がグッと下がります!


無題

Arbor3

Arborはシンプルかつ強力なステートマシンとビヘイビアツリーのグラフエディタです。
デザイナーは挙動を組み合わせて直感的にゲームが作れます。
また、プログラマは自由にステートの挙動をコーディングして追加できます。


Arbor3とはノードを繋いでゲームの挙動を作れるエディタ。
最初から組み込まれている挙動である程度のものは作れるので、初心者でも『考えたゲームの流れを動かす』ことを体験することができますし、ステートを自由にコーディングして作成することが出来るため、初心者を脱した後も便利に使うことが出来ます。
わたし自身、ゲーム開発において要となっているunityアセットです。
注意としては、ノンコーディングやビジュアルスクリプティングではなく、あくまでコーディングの補助アセットなので「もう絶対コーディングしたくない!」って人向けではありません。
代わりに学習コストが大変低く、Arbor3を使うために覚えることはほぼUnityのコーディングにつながって来るんですよね。「きちんとUnityのコーディングを覚えていきたいけれど、如何せん難しい……」と思っている人におすすめです!

大変人気なアセットで、今回のunityアセット真夏のアドベントカレンダー2020でも、すでに8月7日、潮留様が 『Arbor3をおすすめしたい3つの理由』として取り上げています。


また、わたしも過去にArbor3をお勧めする記事を書いております。

その為詳しい説明は割愛いたしますが、Arbor3の利点をかいつまんで説明すると

・挙動の流れが一目でわかるエディタ
・ノードを繋げることで簡単に挙動の内容を変更が可能
・日本人作者によるアセットなのでリファレンスは日本語完備

が挙げられます。


Arbor3のサイトで機能を確認だ!


そんなわけでArbor3の機能や使い方に関しては、既にたくさんの知見がネット上に溢れています。なので今回はちょっと変わったお話をお送りしたいと思います。



parameter containerをデータベースの代用にする。

ノードをつないで挙動を作るステートマシン(FSM)がメインですが、他にも様々な機能があります。その内で、わたしが使い倒しているのがparameter containerです。

parameter containerの公式サイト上の案内は以下の通りです。

ParameterContainerはグラフ間でのデータ共有などを行うために使用するコンポーネントです。
GameObjectにParameterContainerを追加し、各種ある型のパラメータを設定します。


画像7

現在、このような『触手を育てて集めるゲーム』を作っているのですがパッと見の奇抜さとは裏腹に、要は‘アイテムを集めるゲーム’という割とオードソックスなゲームなんですね。
アイテムを集める、となればデータベースの存在は必須です。この内、『アイテム数』などのゲーム進行とともに変化していく数値をparameter containerで管理をしています。

ゲームを作る時、変数という概念は必須。Unityで変数を扱う場合、C#のlist<T>やら配列やらを使うのが一般的だけれど、これが割と初心者殺し。わたしも初心者の時は苦戦しておりました。
そこで、parameter containerを変数を保管するlist<T>の代用として使うことにしました。

スクリーンショット 2020-08-17 0.33.52

parameter containerはunityエディタ上の操作で変数やオブジェクトなどを管理できる機能。

スクリーンショット 2020-08-17 0.34.20

ポチっと『+』を押して……タイプを選択すれば


スクリーンショット 2020-08-17 0.34.42

簡単に要素を増やすことができる〜!!


登録した変数は、Arbor組み込みbehaviorはもちろん、自前で拡張した挙動上でも簡単に呼び出すことが出来ます。具体的な呼び出し方法は過去にnoteに書いたことがあるので見てみてね!


parameter containerをデータベース代わりに使った場合の問題点と解決策。

さて、parameter containerは本来はグラフ間でのデータ共有などを行うために使用するコンポーネントなので、ゲームが再起動入ると数値が初期化されます。
つまり、わたしのように『アイテムの保有数』をparameter containerで管理していた場合、ゲームの再起動が入るとアイテムが全部消えます。
なので、いわゆる『セーブ機能の実装』をしなければいけませんが……いかんせんparameter containerは便利な反面、Arbor3の一部なので外部に保存となるとそのままではちょっと難しいんですよね。PlayerPrefsにしろ、jsonにしろひと手間が必要です。

そこでわたしが行った処理、parameter container全部自動的にList<T>にするを共有したいと思います。



自動的にparametercontainerと同じ要素を持ったlist<T>を生成する。

Arbor3は日本語でのリファレンスが充実しています。リファレンスを参照すれば、Arbor3を自分好みに拡張することが可能です!


parameter containerのリファレンスはこちらになります。



リファレンスを参考に、以下のようなコードを書きました。こちらは実際のコードを汎用的な名前に変え、一部だけ抜き出したものです。またnoteにおけるcode掲載の都合上、細かい改行が入っています。

using System.Collections.Generic;
using UnityEngine;
using System;

//Arbor3の parameter containerを扱う時はusingに以下を入れること!
using Arbor;    

public class  ParameterList : MonoBehaviour
{    
    //アイテムを保存しているパラメーターコンテナの参照
    public ParameterContainer ItemParameterContainer;
   
   
   
   //***Singleton部分***
    //Singlietonのため、インスタンスを生成
   [SerializeField]
   private static ListOfParameterItem mlistOfItem;
   
    //Singleton定義
   public static ListOfParameterItem listOfItem
   {
       get
       {

           if (mlistOfItem == null) mlistOfItem = 
                                      new ListOfParameterItem();

           return mlistOfItem;
       }
   }
   //***Singleton部分おわり***

   
   
   //スタート時に自動でリストに書き込む
   void Awake()
   {
        //要素数に合わせてアイテムデータをリストに生成する
       for (int i = 0; i < ItemNumber; i++)
       {
           //parameterの名前を読み込む
           string ItemName = ItemParameterContainer.parameters[i].name;

           //アイテムデータを生成しリストに書き込む
           listOfItem.ParameterTentaclesList.Add
               (new ParameterItemsData { 
                   Name = ItemName, Number = 0 });
       }
   }
       
       
    //パラメーターをリストに保存する
   void SaveParamInList()
   {
       //アイテムリストの同期
       foreach (ParameterItemsData 
                         ParamItem in listOfItem.ParameterItemsList)
       {
           int Number;
           //名前を取得し、パラメータータイプを反映する
           string Name = ParamItem.Name;
           var ParameterType = ItemParameterContainer.IsParameterType
                                            (Name, Parameter.Type.Int);

           if (ParameterType == true)
           { ItemParameterContainer.TryGetInt(Name, out Number);
                                       ParamItem.Number = Number; }

       }
         
    }
    
  
  
  //プレイヤーの興味が別へうつった瞬間にリストへ書き込む(ブラウザ移動やアプリ移動時)
   void OnApplicationFocus(bool hasFocus)
   {
       if(hasFocus == false)
       {
           SaveParamInList();
           Debug.Log("フォーカスを失ったので:セーブしました");
       }
   }

   private void OnApplicationPause(bool pause)
   {
       if(pause == true)
       {
           SaveParamInList();
           Debug.Log("一時停止されたので:セーブしました");
       }
   }

   private void OnApplicationQuit()
   {
       SaveParamInList();

       Debug.Log("完全終了したので:セーブしました");
   }
    
}


//Monobehavior継承部分おわり------------------

    
  //持ちものデータ
[Serializable]
public class ParameterItemsData
{
   //アイテム名
   [SerializeField]
   public string Name { get; set; } 

   //数値の保存
   [SerializeField]
   public int Number { get; set; }


}
   
   
  //アイテムリスト
[Serializable]
public class ListOfParameterItem
{
   [SerializeField]
   public List<ParameterItemsData> ParameterItemsList 
                                   = new List<ParameterItemsData>();

   // リストを返す
   public List<ParameterItemsData> GetParameterItemsList()
   {
       return ParameterItemsList;
   }

   // 名前でアイテムを取得
   public ParameterItemsData GetParameterItem(string searchName)
   {
       return GetParameterItemsList().Find
                              (name => name.Name == searchName);
   } 
   

今回、わたしが作っているゲームはセーブが1つしか存在し得ないアプリゲームなのでSingletonを併用しています。Singletonもまた、なかなかピーキーなやつなので状況に合わせて除外したりしてください。
では、各コードを細かく見て行ってみよう〜!!


using Arbor;

まずは忘れちゃいけないusing。Arbor3に関連するものを記述するときは、必ずこのように宣言しましょう。忘れると何もできないよ!


    //アイテムを保存しているパラメーターコンテナの参照
    public ParameterContainer ItemParameterContainer;

publicでこのように記述すると、Unity inspector上にparameter containerがアタッチできるようになります。

スクリーンショット 2020-08-17 0.38.42

D&Dでparameter containerをバチっと嵌め込みましょう!
(本番環境ではTentacle parameterになっている図)(名前は適宜、最適なものを名付けしましょう)


  //持ちものデータ
[Serializable]
public class ParameterItemsData
{
   //アイテム名
   [SerializeField]
   public string Name { get; set; } 

   //数値の保存
   [SerializeField]
   public int Number { get; set; }


}
   
   

下部の方にあるこの部分はアイテムデータのclassを定義しています。
アイテム名をstring、アイテム個数をintで定義しプロパティ({ get; set; }の部分のこと)を宣言します。これで、読み込みと書き込みができるようになりました。


  //アイテムリスト
[Serializable]
public class ListOfParameterItem
{
   [SerializeField]
   public List<ParameterItemsData> ParameterItemsList 
                    = new List<ParameterItemsData>();

   // リストを返す
   public List<ParameterItemsData> GetParameterItemsList()
   {
       return ParameterItemsList;
   }

   // 名前でアイテムを取得
   public ParameterItemsData GetParameterItem(string searchName)
   {
       return GetParameterItemsList().Find
                    (name => name.Name == searchName);
   } 

最下部のこの部分でList<T>を定義しています。
先ほど定義したアイテムデータclassのList<T>を定義し、一挙にアイテムデータを扱えるようにしておきます。

これでアイテムデータとListはできたわけだけど、中身は空っぽ、何にもありません。これから、parameter containerの中身を自動的に書き写していきましょう!


   //スタート時に自動でリストに書き込む
   void Awake()
   {
       //要素の数を数える
       int ItemNumber = ItemParameterContainer.parameterCount;
       
        //要素数に合わせてアイテムデータをリストに生成する
       for (int i = 0; i < ItemNumber; i++)
       {
           //parameterの名前を読み込む
           string ItemName = ItemParameterContainer.parameters[i].name;

           //アイテムデータを生成しリストに書き込む
           listOfItem.ParameterTentaclesList.Add
               (new ParameterItemsData { Name = ItemName, Number = 0 });
       }
       Debug.Log("アイテムリストを作成しました");
   }

さて、まずはparameter containerの要素数を数えて同じ数のアイテムデータを生成し、List<T>に書き込んでいきましょう。このコードでは、Arbor3ならではのコードを結構使っています。

       //要素の数を数える
       int ItemNumber = ItemParameterContainer.parameterCount;

parameter container名.parameterCount;はparameter containerの要素数を数えるコードです。
今回は直後のfor文を回す回数を得るために使っています。

           //parameterの名前を読み込む
           string ItemName = ItemParameterContainer.parameters[i].name;

IParameterContainer名.parameters[i].name;でparameter名を取得することができます……が、これはリファレンスに載っていないので割と力技。自己責任で使いましょう!!

           //アイテムデータを生成しリストに書き込む
           listOfItem.ParameterTentaclesList.Add
               (new ParameterItemsData { Name = ItemName, Number = 0 });

そして、取得したparameter名と同じ名前のアイテムデータを生成し、リストに書き込んでいくというわけですね。ここでは数はいったん0にしておきます。

今回はAwake()部分で作業していますがもちろん状況に合わせて適切な場所で行ってくださいね。

これでparameter containerと同じ名前を持ったアイテムデータを同じ数だけ保有しているList<T>が出来上がりましたが、アイテム数は全て0のまま。
次は、アイテム数を読み込んでいきましょう。


    //パラメーターをリストに保存する
   void SaveParamInList()
   {
       //アイテムリストの同期
       foreach (ParameterItemsData ParamItem in 
                        listOfItem.ParameterItemsList)
       {
           int Number;
           //名前を取得し、パラメータータイプを判定する
           string Name = ParamItem.Name;
           var ParameterType = ItemParameterContainer.IsParameterType
                            (Name, Parameter.Type.Int);

           if (ParameterType == true)
           { ItemParameterContainer.TryGetInt(Name, out Number);
                           ParamItem.Number = Number; }

       }
         
    }

SaveParamInList()なるメソッドを宣言し、アイテム保有数をlistに書き込む処理を持つメソッドとして定義します(名付けが良くないのは許してねっ!)
foreach文でアイテムデータList<T>の要素の1つ1つを取り出し、アイテム名をキーにしてアイテム数を読み込んでいきます。こちらも詳細を見てみましょう。

//名前を取得し、パラメータータイプを判定する
           string Name = ParamItem.Name;
           var ParameterType = ItemParameterContainer.IsParameterType
                            (Name, Parameter.Type.Int);

string Name = ParamItem.Name;はList<T>側の名前を読み込んでいます。
そしてItemParameterContainer.IsParameterType(Name, Parameter.Type.Int);は名前をキーにして要素のタイプを判定できるArbor3特有のコードです。今回はintかどうかを判定しています。

 if (ParameterType == true)
           { ItemParameterContainer.TryGetInt(Name, out Number);
                            ParamItem.Number = Number; }

intだったらアイテムデータにアイテム数を書き込む処理をします。
ItemParameterContainer.TryGetInt(Name, out Number);は名前をキーにしてintのparameterを読み込み、Numberという変数に書き込む、という意味のコードです。
最初にintかどうか判定していたのは、ヒューマンエラーによりint以外のタイプがparameter containerに登録されていた場合、なんかやばいことになるのを回避するために入れております。
今回はアイテム数の保存、という目的のためintになっていますが、何のデータを保存するかによってタイプは変わりますので状況に応じて適切なタイプを使っていきましょう!


  //プレイヤーの興味が別へうつった瞬間にリストへ書き込む(ブラウザ移動やアプリ移動時)
   void OnApplicationFocus(bool hasFocus)
   {
       if(hasFocus == false)
       {
           SaveParamInList();
           Debug.Log("フォーカスを失ったので:セーブしました");
       }
   }

   private void OnApplicationPause(bool pause)
   {
       if(pause == true)
       {
           SaveParamInList();
           Debug.Log("一時停止されたので:セーブしました");
       }
   }

   private void OnApplicationQuit()
   {
       SaveParamInList();

       Debug.Log("完全終了したので:セーブしました");
   }

あとは定義したSaveParamInList()メソッドを必要に応じて呼び出せば、Listが更新されていくというわけです。今回、List<T>はおいおいセーブデータとしてjsonに書き出す処理に使うため、ユーザーがゲームプレイを辞めそう、というときに保存する仕様になっています。


parametercontainer⇒list<T>⇒jsonに変換する場合の注意。

parameter containerをList<T>にした後、さらにjsonに書き出す……ということは結構需要があると思います。jsonへの書き出し、読み込みはセーブデータの作成方法として一般的だからです。
(今回はjsonの書き出しについては記載しません。jsonも奥が深いからね!)

コードをみると、ちょいちょい

  //持ちものデータ
[Serializable]
public class ParameterItemsData
{
   //アイテム名
   [SerializeField]
   public string Name { get; set; } 

   //数値の保存
   [SerializeField]
   public int Number { get; set; }


}

[Serializable]という宣言がくっついているのにお気づきになりましたか?
これは簡単にいうとjsonとか他の形式に変換してもいいやで〜という許可です。この[Serializable]を宣言し忘れるとjsonにできませんので注意しましょう!

詳しくはUnityのマニュアルを見てみよう!


list<T>からparameter containerにすることも可能。


    //*リストの値をパラメーターに入れる(ロード時に使う)
   void LoadParamFromList()
   {
       //アイテムリストの同期
       foreach (ParameterItemsData ParamTentacle in
                         listOfItem.ParameterItemsList)
       {
           //名前を取得し、パラメータータイプを反映する
           string Name = ParamItem.Name;
           var ParameterType = ItemParameterContainer.IsParameterType
                            (Name, Parameter.Type.Int);

           if (ParameterType == true)
           { int Number = ParamItem.Number;
               ItemParameterContainer.SetInt(Name, Number); }
         
       }
   }

もちろん、List<T>の数値をparameter containerに保存することもできますよ!



Arbor3はリファレンスも充実!!

さて、途中で紹介をしましたが改めまして……Arbor3はリファレンスが大変充実しており、設計次第で柔軟な対応が可能です。

parameter containerとノードグラフの相性の良さは魅力的ですが、parameter container→List<T>→jsonという変換は割と二度手間だしList<T>を直に読み込むような設計にした方が捗るかもしれません。
その気になればなんでもかんでもArbor3の機能で作れはするのですが、回りくどくなってしまうこともあるので、作りたいプロジェクトに合わせて最適な設計で活用していきましょう!

無題

Arbor3

皆さんもリファレンスを活用して素敵なArbor3ライフを送ってくださいね!



宣伝

Arbor3を活用して作ってるゲームはこちらだよ、よろしくね!


この記事が気に入ったらサポートをしてみませんか?