📊

Unity Graph Toolkit(experimental版)を使ってみました

に公開

はじめに

現在開発中のゲームの敵の行動パターンをビヘイビアツリーを使って実現しようとしています。

最初なのでそんなに複雑な行動も作らないだろうという想定で、基本的な仕組みだけ実装を行い、構造はScriptableObjectにSerializeReferenceを使ってビヘイビアツリーの形式で行動情報を構築するという形で実装を行っていました。

編集はInspectorから行い、将来的にGraph Toolkitの正式版(1.0版)がリリースされたらグラフでの編集ツールを作れば良いかなと思っていたのですが、想定以上に行動パターンが複雑化して、流石に使い辛いという話になりました。

そこで、まだExperimental版ですがGraph Toolkitを使ってみることにしました。
https://docs.unity3d.com/Packages/com.unity.graphtoolkit@0.4/manual/introduction.html

課題

Graph Toolkitによる編集ツール自体は比較的簡単に実装ができました。
問題は、既存のデータをどうやってグラフデータに変換するかでした。

Graph ToolkitはまだExperimentalなので、正式版でフォーマットが変更される可能性があり、そのことも考慮する必要がありました。

最終的に、既存の行動情報を格納したScriptableObjectとグラフデータのコンバーターを実装する形で対応を行いました。

ScriptableObject → グラフデータ

調査した限りでは、グラフデータを直接構築する方法が公開されていなかったため、最終的にYAMLファイルを直接生成する処理を実装しました。

かなり無理矢理で長いので詳細の実装は省略しますが、雰囲気としては以下のような感じです。

    void ConvertData(EnemyBehaviorSetData data)
    {
        var inputPath = AssetDatabase.GetAssetPath(data);
        var outputPath = Path.ChangeExtension(inputPath, "enemybehaviorgraph");
        outputPath = PathUtil.Rename(outputPath, "$Graph");
        Debug.Log($"Convert: {outputPath}");

        var ridGenerator = new UniqueReferenceIdGenerator();
        var graphModel = CreateNodes(ridGenerator, data.Behavior);
        var sectionModelRid = ridGenerator.GenerateId();
        var graphModelRid = ridGenerator.GenerateId();
        var graphRid = ridGenerator.GenerateId();

        var yaml = new YamlBuilder();
        yaml.AppendLine("%YAML 1.1");
        yaml.AppendLine("%TAG !u! tag:unity3d.com,2011:");
        yaml.AppendLine("--- !u!114 &1");
        yaml.AppendLine("MonoBehaviour:");
        using (new YamlBuilderIndentScope(yaml))
        {
            yaml.AppendLine("m_ObjectHideFlags: 61");
            yaml.AppendLine("m_CorrespondingSourceObject: {fileID: 0}");
            yaml.AppendLine("m_PrefabInstance: {fileID: 0}");
            yaml.AppendLine("m_PrefabAsset: {fileID: 0}");
            yaml.AppendLine("m_GameObject: {fileID: 0}");
            yaml.AppendLine("m_Enabled: 1");
            yaml.AppendLine("m_EditorHideFlags: 0");
            yaml.AppendLine("m_Script: {fileID: 11500000, guid: 790b4d75d92f4b0984310a268dbd952f, type: 3}");
            yaml.AppendLine("m_Name: EnemyBehaviorGraphTest2");
            yaml.AppendLine("m_EditorClassIdentifier: Unity.GraphToolkit.Editor::Unity.GraphToolkit.Editor.Implementation.GraphObjectImp");
            yaml.AppendLine("m_GraphModel:");
            using (new YamlBuilderIndentScope(yaml))
            {
                yaml.AppendLine($"rid: {graphModelRid}");
            }
            ...(以下略)

グラフデータ → ScriptableObject

こちらは未対応ですが、簡単にできると考えています。
Graph Toolkitのサンプルにもあるように、ScriptedImporterを使えば、ScriptableObjectとして扱うことが可能です。
あとは、このデータを本当のScriptableObjectとしてコピーする部分だけ作れば良さそうです。

良かったところ

InspectorだとUnityが重くなってしまっていましたが、グラフだと快適に動作しています。
また見た目もInspectorよりだいぶ見やすくなったと好評でした。

困ったところ

出力ポートの数を、可変長にしたかったのですが、今の所そのような機能を実現するための機能を見つけられませんでした。

ワークアラウンドとして、以下の対応で(無理矢理)対処しました。

  1. 可変長にしたい出力ポートを持つノードに、ポート数(Length)と更新フラグ(Update)を追加
  2. GraphのOnGraphChangedで、全てのノードを走査し、1.のノードのUpdateがtrueになっているノードを見つけたら、NodeのDefinePortを呼び出してNodeの更新を行う
  3. 最後にUpdateをfalseに戻すために、UpdateポートのEmbeddedValue.ObjectValue = falseをリフレクションを通して呼び出す

Lengthを変更し、Updateをtrueにした時点で内部的には更新が行われているのですが、
グラフ上でノードを少し動かさないと表示が反映されません・・・。
(リフレクションで頑張れば即時反映もできるかもしれませんが、あまり時間をかけたくなかったので、一旦これでヨシとしました)

まとめ

ワークアラウンドばかりではありますが、今後のことを考えて対応できたかなと思っています。
Graph Toolkitはまだ機能は多くありませんが、今後のバージョンアップに期待しています。

Happy Elements

Discussion