Arbor3とLogic Toolkitの違いや移行すべきかについて

前作「Arbor 3: FSM & BT Graph Editor(以下Arbor3)」と今作「Logic Toolkit」の違いやArbor3から移行すべきかについて、開発経緯なども含めて説明いたします。

Arbor3について

Arbor3についてご存じない方は以下の公式サイトやドキュメントをご覧ください。

Arbor3はすでに新機能開発を終了しており現在はサポートのみの期間に入っております。

Logic Toolkitについて

Logic Toolkitについてご存じない方はこの公式サイトやドキュメントをご覧ください。
無料試用版もありますので、ご購入前にお試しください。

対応UnityバージョンはUnity6以降です。

Logic Toolkitを開発した経緯

Arbor3とLogic Toolkitはどちらも挙動制御用グラフエディタアセットであり、機能面もほぼ似通っています。
なぜArbor3があるのにLogic Toolkitを開発することにしたかは、Arborの内部実装に限界を感じたからです。

ArborはMonoBehaviourベース

Arborのver1開発当時はまだUnity4でした。
開発当初、ステートマシンをノードグラフで組みステート用スクリプトもユーザーが作成して拡張できるようにしたいと考えた結果、スクリプトで拡張可能なMonoBehaviourベースで開発することにしました。

それからバージョンアップでいろいろな機能を追加してきましたが、根本的な仕様は今も変わっていません。
つまり今もArbor3のノード用スクリプト(StateBehaviourやActionBehaviour、Calculator)はMonoBehaviourを継承して作成されています。
Stateノードに各種StateBehaviourを追加すると内部的にはArborFSMと同じGameObjectにAddComponentされ、ステートのアクティブ状況に応じてStateBehaviourも切り替わる仕様です。
多くのコンポーネントを追加することになるため、Inspectorには表示されないようにHideFlags.HideInInspectorなどを設定し隠蔽していますが、データ構造上はUnityのGameObjectとコンポーネントの仕様に完全に依存しています。
あくまでMonoBehaviourなのでOnCollisionEnterメソッドなどをノード用スクリプトでも使用できる利点はありますが「グラフデータのみのアセット化」は不可能であり、ちょっとしたグラフを使いまわす場合でもプレハブのInstantiateをしてGameObjectとして動作させなければいけません。

Logic ToolkitはSerializeReferenceベース

「グラフデータのみのアセット化」ができて、より柔軟にグラフを扱えるようにするには?
と考えていたところ、UnityのバージョンアップによりSerializeReferenceが追加されたためより柔軟な多態性(ユーザーもスクリプト書ける)が実現できるようになりました。
これは、MonoBehaviourかどうかに関係なく、派生型をシリアライズできることを意味します。

例えば以下のコードがあるとします。
(あくまで例なのでこのコードに意味があるのかは気にしないでください)

public interface IScript
{
    void Update();
}

[System.Serializable]
public class UserScript1 : IScript
{
    public void Update()
    {
        // なにか1
    }
}

[System.Serializable]
public class UserScript2 : IScript
{
    public void Update()
    {
        // なにか2
    }
}

public class FooComponent : MonoBehaviour
{
    [SerializeReference]
    private IScript script; // UserScript1のインスタンスもUserScrtipt2のインスタンスも格納できる

    void Update()
    {
        script?.Update();
    }
}

public class BarAsset : ScriptableObject
{
    [SerializeReference]
    private IScript script; // こっちもUserScript1のインスタンスもUserScrtipt2のインスタンスも格納できる

    public void Update()
    {
        script?.Update();
    }
}

このようにSerializeReferenceを使用することで、コンポーネントであるかアセットであるかの区別なくUserScript1やUserScript2のインスタンスを格納できます。
これでオブジェクト指向の多態性(基本クラスやインターフェイスを介すことで各インスタンス側の内部実装に依存せずにメンバーを呼び出せる手法)が実現できますので、グラフデータのアセット化ができるように可能になります。

ただし、MonoBehaviourだったものをそのままSerializeReferenceに移行してもUnity内部のシリアライザは移行処理をしてくれませんし、多数のArbor3ユーザーにまで個別対応してもらうのも無理があります。
例え互換性がなくても利便性が向上するならば、とSerializeReferenceベースでの次世代Arborを別途新規で開発することに決めました。
Arbor開発を続けて10年、Logic Toolkitはこれからの10年に耐えられるアセットにしよう、という想いで開発してきました。

変わらないこと

基礎理念はArbor3とLogic Toolkitで変わりありません。
主に挙動制御をノードグラフで組み視覚化することで動作確認しやすくするためのアセットです。

  • ステートマシンやビヘイビアツリーに対応
  • ユーザー独自にノード用スクリプトを書いて拡張しやすい
  • グラフ上ではノードの流れと設定値などを視覚的に確認しやすい

このような点はあえてほぼ同じような使い勝手にしてArbor3の良い点を引き継ぐように意識して開発しております。

主な改善点

Logic Assetや関数グラフアセットが使用可能

前述の通り、Arbor3では実現不可能だった「グラフのアセット化」がLogic Toolkitでは使用できます。

これは索敵と攻撃を切り替えるイメージ図です。
実際はアニメーションや弾丸の発射などの制御も行う必要があります。

共通の挙動をLogic Assetで作成しておき、複数のLogic Playerから同じLogic Assetを参照して各オブジェクトを制御できます。
同じLogic Assetで挙動の造りは同じであってもInspectorで変数値を変更すればパラメータの違いにも対応できます。

関数グラフアセットも同様で、例えばダメージ計算式のグラフなどをアセット化しておくことでどこからでも共通のダメージ計算が利用できるようになります。

ダメージ量 = 攻撃力/2 - 防御力/4の計算

またLogic Behaviorコンポーネントを使用すれば、従来通りコンポーネントに直接グラフを持たせられます。
シーン内やプレハブ内のオブジェクト参照はコンポーネントから行う方が楽にできますので、わざわざアセットを介さなくてもグラフを作れるようにしています。

Logic Behaviorコンポーネントであればシーン内の他オブジェクト(今回はMain Camera)を直接指定できる例

ノード用スクリプトの汎用性向上

Arbor3では、StateノードにはStateBehaviour、BehaviourTreeのノードにはActionBehaviourなど、ノードの種類によって使えるノード用スクリプトが決められていました。
ほぼ同じ処理をしているのにStateBehaviourとActionBehaviourの両方のスクリプトを用意しなければいけないのは組み込みスクリプトを用意していた自分でも煩わしく感じていました。
StateBehaviourやActionBehaviourを使いまわす設計に変えることも検討しましたが、結局既存スクリプトを移行するにはすべて個別対応せざるを得ません。

Logic Toolkitでは、Arbor3からのデータの互換性を考慮する必要もないので、ノードとノード用スクリプト間の役割の違いを吸収する内部的な橋渡し機能を実装して実現しました。
これにより、例えばActionComponentスクリプトを一つ作れば、Actionノード・Stateノード・BehaviourTreeノード・Serviceノードなどのどれでも同じスクリプトを使えます。

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

[System.Serializable]
public class DebugAction : ActionComponent
{
    [SerializeField]
    private InputField<string> message;

    // OnAction is called when the node is executed.
    protected override void OnAction()
    {
        Debug.Log(message.Value);
    }
}

ノードの実行の流れ

Arbor3ではステートマシングラフとビヘイビアツリーグラフをそれぞれ別途作成し個別に動作する仕様になっていました。

Logic Toolkitの開発当初もステートマシングラフを作成する方向性で考えていましたが、「ステートもビヘイビアツリーも本質はタスク(実行と完了待ち)じゃない?」と思い、実行中ノードが接続先のノードも実行する連鎖の中にステートマシンもビヘイビアツリーも混在して組み込めることに気が付きました。
Logic Toolkitではその考えを採用し、各種グラフ対応の代わりに、ノードが接続先ノードを実行する流れ(実行フロー)をベースに、StateノードやBehaviorTreeノードも混在できるシステムとしました。

実行フロー、ステート、ビヘイビアツリー、データフローの混在グラフの例

ただし単純なステートマシングラフではなくなった弊害として、外部スクリプトから直接遷移はできなくなりました。
ノード間の接続もなく飛び飛びに実行されるのはあまり推奨できない使用方法ですので、現時点では同様の機能は対応しない予定です。
ただ何か良い案があればフォーラムなどに投稿していただければ検討いたします。

Blackboard

Blackboardはグラフ用変数管理機能で、Arbor3ではParameterContainerという名前です。
Arbor3のParameterContainerでは決まった型のみ変数として利用できます。
変数データの格納のためだけにMonoBehaviourでの多態性を利用するのは無駄が多いため、内部的に固定の各種型データリストを保持し、インデックス指定でリストの要素にアクセスする仕様になっています。
ユーザー定義の型は当然利用できませんし、Vector3Int型などが追加されたときにParameterContainerにも追加対応しましたが変更箇所が多く、さらに今後増えてもその都度対応するのが難しい設計だと痛感しました。

Logic Toolkitでは固定の型で内部的に直接実装せずSerializeReference経由に任せていますので、今後Unityが更新されて新たな型が追加されてもユーザーが型を定義しても自由に利用できるようになりました。

また、同じBlackboardを共有する仕組み(名前の由来である黒板モデル)としてのData Link機能も実装しています。
Data Linkを利用すれば、シーン内の「チームA」と「チームB」のそれぞれの陣営のキャラ同士でしか共有できない戦況データなどを作成できます。

メンバーアクセスのスクリプト自動生成

Arbor3では、メソッド呼び出しなどはユーザーに個別の呼び出しスクリプトを書いていただくかリフレクション経由で呼び出すかのいずれかしかできませんでした。

Logic Toolkitでは、Roslyn Analyzerの一種であるIncremental Source Generatorでのメンバーアクセス用スクリプトの自動生成に対応しました。
ノード作成メニューのMembersタブでアクセスしたいメンバーとアクセス方法を選択すれば、自動的にコードが生成されます。

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

#pragma warning disable 0612
#pragma warning disable 0618

namespace LogicToolkit.GeneratedScripts
{
    [System.Serializable]
    [ScriptGenerated("Assets/Logic Toolkit/Generated Scripts/LogicToolkitGeneratedScripts/LogicToolkitGeneratedScripts.LogicToolkit_ScriptGenerator.additionalfile", 0)]
    [MenuName("/LogicToolkitGeneratedScripts/UnityEngine/Random/Get Random.value")]
    [MemberIconAttribute(MemberIconKind.StaticProperty)]
    [DefaultRecomputeMode(RecomputeMode.Node)]
    internal sealed class Generated_Get_UnityEngineːRandomːvalue : ActionComponent
    {
        [SerializeField]
        private OutputDataPort<float> _value = new OutputDataPort<float>();

        protected override void OnAction()
        {
            _value.SetValue(global::UnityEngine.Random.value);
        }
    }
}

生成したコードはソースファイルとしてUnityのプロジェクト内に保存されるわけではなく、あくまでRoslynが管理するコードとして生成されるため、細部を直接書き換えることなどはできません(IDEで生成されたコートの閲覧はできます)。
ファイルに保存しない理由は、例えばUnityの更新などで呼び出し対象のメンバーが後から変更された場合にエラーになったとしてもユーザーがコードを手修正することなくエディタ側である程度修正して再生成するためです。

組み込みスクリプトをほぼ廃止

Arbor3には指定秒数待つTimeTransitionや四則演算系のCalculatorなどの基本的なスクリプトだけでなく、NPCの移動&アニメーション制御のためのAgentControllerなども組み込んでいました。
ある程度スクリプトを書かなくても良いように事前に組み込んでいたものの、各ユーザーがそれぞれ作りたいゲームの仕様にあわせたものを想定して提供することは難しく、結局ほとんどの組み込みスクリプトを使わずに自作するユーザーが多かったかと思います。

Logic Toolkitでは前述のメンバーアクセス用スクリプトの生成機能もあるので、主要機能はLogic Toolkitと切り離してユーザーがお好みで実装し、そのメンバーアクセスを行う部分だけを自動生成できます。
もちろんノード用の独自スクリプトも書けます。
どのように使われるかわからない組み込みスクリプトへの開発コストも削減できるため、全面的に廃止することとしました。

Arbor3のAgentControllerやGameObjectプール機能などもなくなりましたが、必要に応じてArbor3本体から一部コードを移植して使ってもらっても構いません。
ただし、そのまま抜き出してオープンソース化するなどのライセンス違反行為は厳禁です。
また移植の需要が多そうでしたら完全に切り離した単独アセットとして販売する可能性もあります。

移行すべきか?

ここまでで経緯や内部的な違いは分かっていただけたでしょうか。
最後に、移行すべきかについて開発者としての考えを記載いたします。

既存プロジェクト

既存プロジェクトから特に急いで移行する必要はありません。
また、移行のために急いでプロジェクトをUnity6にアップデートする必要もありません。

現在Unity2019からUnity6までの各LTSで動作しており微細な不具合があればその都度修正対応しますので、すぐにArbor3が公開停止されることはありません。
その点はご安心ください。

移行する場合、Arbor3とLogic Toolikitではデータの互換性がなくArbor3用に書いたスクリプトなどもLogic Toolkitではそのまま使用できないため、完全移行するにはほとんどの書き直しが発生します。
使用してみたくなった場合でも、それらの移行作業量や仕様の差異なども含めて検討してみてください。

新規プロジェクト

新規プロジェクトではLogic Toolkitの利用を推奨します。

ArborがUnity4時代の基礎設計であるのに対し、Logic Toolkitでは最新Unity6向けの設計となっているため断然利便性があがっております。

仕様の差異もありますので学習コストが発生しないわけでもありませんが、基本理念はArbor3から同じであるため使い勝手が極端に変わって混乱するようなこともないかと思っています。

試用版

冒頭にも書きましたが、無料試用版もありますのでご購入前にお試しください。

試用版

試用版でもUnityエディタ上での機能制限はほとんどありませんので、エディタの操作感の確認だけでなく実際のプロジェクト内での活用方針まで検討できるかと思います。

補足

ArborやLogic Toolkitに関係なくどのようなアセットであっても、機能追加の要望やUnityの破壊的変更に対応するならそれ相応の作業がアセット開発者に発生します。
状況によっては互換性を維持しての対応が難しい場合もあります。
Unityの開発環境も日々変わっており、今後も互換性を維持するか新規で開発するかを選択する時が来るのだと思います。

このことをご理解ご了承のうえで、今後ともアセットのご利用をお願いいたします。