Help us understand the problem. What is going on with this article?

Unity Behavior DesignerからADX2の再生制御を行う

Behavior DesignerとADX2の連携機構を作る

本投稿では、Unity開発環境においてBehavior Tree開発用アセットのBehavior DesignerとサウンドミドルウェアADX2の連携方法を紹介します。

CRI ADX2を初めて利用する場合は、先に次の投稿をご覧ください。

Unity: Unityのサウンド機能をADX2で強化する
https://qiita.com/Takaaki_Ichijo/items/16e6501fc07f5b3b3377

Behavior Designerとは

Behavior Treeという挙動の記述モデルをUnityで作成するためのアセットです。アセットストアで定価80ドルで販売されています。
挙動(Behavior)を木構造で記述するもので、ゲームではキャラクターのAI制御に使われています。

behavior-designer.png

https://opsive.com/support/documentation/behavior-designer/overview/

様々な条件と組み合わせて、AI制御キャラクターがゲーム内の状況に応じてどのような動きをするかGUIで設計してくことができます。

ADX2と組み合わせるメリット

Behavior DesignerではUnity標準のサウンドを再生するタスクが用意されています。AudioClipを再生・停止したり、音量などのパラメータを変更することもできます。しかし、キャラクターの挙動や状態に合わせて音を再生しようとすると、複雑なタスク設計が必要になってきます。

たとえば「剣を振る音」を再生したいとして、画面内にキャラクターが10体居たとしましょう。たまたまAIの判定により10体全員が同時に剣を振った時、同じ音が重なって鳴らないように制御することはかなり難しそうです。

そこで、ADX2の機能が生きてきます。ADX2はサウンドデータを直接再生するのではなく「キュー」と呼ばれる単位で音を扱います。キューには複数のサウンドデータと、さまざまな再生制御情報を入れることができます。上記の例でいえば、キューに対して「ゲーム全体での再生上限数」を設定できるので、ゲームから大量の再生リクエストが来ても、実際に鳴る音は一定数以下に制限できます。

また、キャラクターの足音を鳴らしたい時、左右の足音を交互に鳴らす、といったような異なるサウンドデータを一定のルールで鳴らす仕組みをデータ側に埋め込むことができます。ボリュームやピッチ変更、フィルタの適用などのエフェクトに関しても、ADX2の「AISAC」という操作パラメータの経由で複数のエフェクトをまとめて制御できます。

AISAC機能の例については、次の投稿をご参照ください。

ゲームのUIでボタン音を操作やカーソル位置に応じて変える
https://qiita.com/Takaaki_Ichijo/items/d4804fcc641f046d68a7

連携方法

Behavior Designerは、開発者が独自のタスクを開発できるように、拡張用の基底クラスが用意されています。「アクション」タスクを作りたい場合は、BehaviorDesigner.Runtime.Tasks名前空間のActionクラスの派生クラスを作るだけで、Behavior Designerのエディタ内で使えるタスクを作成できます。

WRITING A NEW ACTION TASK
https://opsive.com/support/documentation/behavior-designer/writing-a-new-action-task/

ADX2制御用アクションの開発

CRI ADX2には様々なサウンド制御機構がありますが、Behaviour Designerは主にゲーム内のキャラクターAI制御に使われますので、キャラクターが音を発するときに使いそうな機能を呼び出すことができれば良さそうです。
今回は「キューの再生」「キューの停止」「AISAC値の設定」の3つのアクションを実装します。

アイコン画像にはAssets/Gizmos/CriWareに入っているものを流用します。

PlayStopCueBehaviourDesigner.png

Behavior Desingerのウィンドウでは、右クリックメニューに「ADX2」の項目が追加され、これら3つのアクションを配置できます。

BehaviorDesingerADX2Menu.png

キューの再生

なにがともあれ、サウンドの再生を行わなくてはなりません。

ADX2でのサウンド再生には、「再生するサウンドデータが入ったファイル(キューシート)」のロードと、サウンドの再生単位である「キュー」の名前指定が必要です。

Behaviour Designerのアクションとしてキューシートのロード機構も作れると思うのですが、データの読み込みと破棄はAI制御の内部でやらないほうが良い気がしますので、今回はCRIAtom.csのインスペクターで読み込み設定がされているものとします。
コードは次の通りです。

PlayAtomSource.cs
using BehaviorDesigner.Runtime;
using BehaviorDesigner.Runtime.Tasks;
using UnityEngine;

[TaskCategory("ADX2")]
[TaskIcon("Assets/Gizmos/CriWare/VoiceOn.png")]
public class PlayAtomSource : Action
{
    public SharedGameObject atomSourceGameObject;
    public SharedString cueSheetName, cueName;

    private CriAtomSource atomSource;
    private GameObject prevGameObject;

    public override void OnStart()
    {
        var currentGameObject = GetDefaultGameObject(atomSourceGameObject.Value);
        if (currentGameObject != prevGameObject)
        {
            atomSource = currentGameObject.GetComponent<CriAtomSource>();
            prevGameObject = currentGameObject;
        }
    }

    public override TaskStatus OnUpdate()
    {
        if (atomSource == null)
        {
            Debug.LogWarning("Atom Source is null");
            return TaskStatus.Failure;
        }

        if (string.IsNullOrEmpty(cueName.Value))
        {
            Debug.LogWarning("Cue Name is empty");
            return TaskStatus.Failure;
        }

        if (string.IsNullOrEmpty(cueSheetName.Value))
        {
            Debug.LogWarning("Cue Sheet Name is empty");
            return TaskStatus.Failure;
        }

        if (CriAtom.GetCueSheet(cueSheetName.Value) == null)
        {
            Debug.LogWarning("Cue Sheet is not loaded");
            return TaskStatus.Failure;
        }

        atomSource.cueSheet = cueSheetName.Value;
        atomSource.Play(cueName.Value);

        return TaskStatus.Success;
    }
}

このアクションのインスペクターでは、

  • Atom Sourceがアタッチされたゲームオブジェクトの参照
  • キューシート名
  • キュー名

の3つのパラメーターを指定します。

PlayAtomInspector.png

「Atom Sourceがアタッチされたゲームオブジェクトの参照」には、SharedGameObjectクラスでatomSourceGameObjectフィールドを用意しています。これは、Behavior Designerが持つクラス参照の共有機構を使うためです。SharedGameObjectクラス経由でAtom Sourceを取得するので、ローカル変数としてAtom Sourceの参照を持っておき、複数のアクションで使いまわすことができます。

Behavior Designerエディタウィンドウの「Variables」で、アクションをまたぐゲームオブジェクトの参照を保持できます。
AtomSourceがアタッチされたゲームオブジェクトの参照を「AtomSourceGameObject」として以下のように定義し、シーン上のゲームオブジェクトの参照を保持します。

BD_Variables.png

タスクのインスペクターでフィールド左の「・」ボタンをクリックして、「AtomSourceGameObject」を指定します。

BD_Action.png

これにより、複数のアクションに毎回AtomSourceの参照をつけずに済みます。

キューの停止

キューの停止はシンプルで、Atom Sourceに対してatomSource.Stop()を呼ぶだけです。

StopAtomSource.cs
using BehaviorDesigner.Runtime;
using BehaviorDesigner.Runtime.Tasks;
using UnityEngine;

[TaskCategory("ADX2")]
[TaskIcon("Assets/Gizmos/CriWare/VoiceOff.png")]
public class StopAtomSource : Action
{
    public SharedGameObject atomSourceGameObject;

    private CriAtomSource atomSource;
    private GameObject prevGameObject;

    public override void OnStart()
    {
        var currentGameObject = GetDefaultGameObject(atomSourceGameObject.Value);
        if (currentGameObject != prevGameObject)
        {
            atomSource = currentGameObject.GetComponent<CriAtomSource>();
            prevGameObject = currentGameObject;
        }
    }

    public override TaskStatus OnUpdate()
    {
        if (atomSource == null)
        {
            Debug.LogWarning("Atom Source is null");
            return TaskStatus.Failure;
        }

        atomSource.Stop();

        return TaskStatus.Success;
    }
}

AISACの設定

AISACを使って、AIの挙動にあわせて音が変わる仕組みを作ることができます。
こちらも、Atom Sourceの参照を取得して、タスクのインスペクターに指定された値を与えるだけのものです。

SetAisac.cs
using BehaviorDesigner.Runtime;
using BehaviorDesigner.Runtime.Tasks;
using UnityEngine;

[TaskCategory("ADX2")]
public class SetAisac : Action
{
    public SharedGameObject atomSourceGameObject;
    public SharedString aisacControlName;
    public SharedFloat aisacValue;

    private CriAtomSource atomSource;
    private GameObject prevGameObject;

    public override void OnStart()
    {
        var currentGameObject = GetDefaultGameObject(atomSourceGameObject.Value);
        if (currentGameObject != prevGameObject)
        {
            atomSource = currentGameObject.GetComponent<CriAtomSource>();
            prevGameObject = currentGameObject;
        }
    }

    public override TaskStatus OnUpdate()
    {
        if (atomSource == null)
        {
            Debug.LogWarning("Atom Source is null");
            return TaskStatus.Failure;
        }

        if (string.IsNullOrEmpty(aisacControlName.Value))
        {
            Debug.LogWarning("AisacControlName is Empty");
            return TaskStatus.Failure;
        }

        if (aisacValue == null)
        {
            Debug.LogWarning("AisacValue is Empty");
            return TaskStatus.Failure;
        }

        atomSource.SetAisacControl(aisacControlName.Value,aisacValue.Value);

        return TaskStatus.Success;
    }
}

AISACコントロール名も複数のBehavior Desingerタスクで共有して使いまわすことが多いと予想できるので、Variablesタブで定義しておくと便利です。

距離アイザックのコントロール名を宣言.png

まとめ

Behavior Designerへタスク追加は、以前チャレンジしたDoozy UIの拡張と比較して非常に簡単に追加できます。

ADX2で言えばブロック再生のブロック遷移や、セレクタ機能の利用なども、通常のメソッド呼び出しと変わらない感覚で実装できます。本投稿を参考にしてチャレンジしてみてください。

Takaaki_Ichijo
Indie game developer: Back in 1995 pc/mac/consoles. Developer relations for game dev tools, Unityチョトデキルおじさん
http://head-high.com/
headhigh
オリジナルゲームタイトルの開発、ゲーム開発者向けツール・ミドルウェアのビジネスコンサルティングを行っています。
https://head-high.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした