以下の内容はhttps://orotiyamatano.hatenablog.com/entry/Behaviorより取得しました。


【アドカレ】(Unity Behavior) UnityでゲームAIを作る!

こんにちは、やまだたいし( https://twitter.com/OrotiYamatano )です。
本記事は Unityゲーム開発者ギルド Advent Calendar 2024 14日目の記事です。

遅刻しました。
皆様はUnity Behaviorというものを知っていますでしょうかMonoBehaviourではないです。
Unity Behaviorです。
Unity Behavior は、ノンプレイヤーキャラクター (NPC) またはオブジェクトを制御するために使用される動作を作成するためのUnity公式のツールです。
今回はそのUnity Behaviorについての記事です。

目次


Behaviorとは


記事書きましたが、利用非推奨にさせてもらいます
というのもUnity公式サポートが取りやめになるらしいです
参照リンク

Unity公式のBehaviorTreeの作成ツールです。

docs.unity3d.com

今回はそれの利用方法の基礎です。

利用バージョン


Windows
Unity6000.0.30f1
Behavior 1.0.6

を今回は利用

導入


Unityを立ち上げたらWindow→PackageManagerより Unity Repositryを選択し、
Behaviorパッケージを検索し、ダウンロードインポートする。

Projectの空いてるところで右クリック
Create→Behavior→Behavior Graphを選択

今回はEnemyと命名しました。

ダブルクリックをするとGUIが立ち上がる

AIを動かしたい方にはAdd Componentより
Behavior Agentを追加

インスペクタのBehaviorGraphの空いているところに先程のGraphをドラック&ドロップしてアタッチしておく

実際に使ってみる


ノードグラフの空いている箇所を右クリックすると様々な挙動がデフォルトで実装があるのですが、

このあたりはパッケージがアップデートされると変わってしまう可能性もあるので今回は重要な機能はデフォルトのものは使わないで自分でかいてみようと思います。

Create newをすると
・Action
・Modifer
・Sequencing
があります

(Joinというのもあるけど割愛)

各ノードにはステータスがありステータスは
・Running:実行中
・Waiting:子ブランチの実行待機中
・Succeeded:ノードの成功
・Failed:ノードの失敗
・Uninitialized:ノードはまだ開始されていない
というステータスがある。

それぞれの挙動はこうです

・Action


動作を司るもの
子ノードがなく
歩いたり、攻撃したり、モーションを挙動させたりする。

動作の結果を親に渡し、
成功ならsuccess,
失敗ならfailuer
をそれぞれ渡します

・Modifer


動作グラフの実行フローの制御です。
子ノードを一つだけ持つことができます。
簡単にいうとぶら下げてる子ノードを実行するかどうか決める役割。
デフォルトでは
・Repeat(繰り返し)
・Repeat Until Failure(失敗するまで繰り返し)
・Repeat Until Success(成功するまで繰り返し)
・Repeat While Condition(条件が成立している間繰り返し)
・Succeeder(常に成功として扱う)
・Inverter(結果を反転させる)
・OnStart(開始時に実行)
・Abort(中止)
・Restart(再開始)
がある

スクリプトで書く場合は継続条件をコーディングする形になる。

・Sequencing


数の子ノードを実行するもの
子ノードが複数ある
一番単純なものは順番に実行するもの。

今回作るもの


今回は単純なものを作ろうと思います。

・プレイヤーを探す
・プレイヤーを見つけたら追いかける
・巡回する

Modiferでプレイヤーを探す


まず、プレイヤーが視界に入ったらという条件Modiferを追加します。
Findというデフォのものもありますが自前で書きます。

グラフの上で右クリック
Create new→ Modifier

今回新規のメニューに入れたいのでカテゴリはnewで名前がTest
名前はFindPlayerにした。

ここは自然言語的な入力をするとデフォルトで入力項目が現れます。

Repeat While Player Is in Sight(プレイヤーが視界にある間繰り返す)

するとこのようなスクリプトが生成されました

using System;
using Unity.Behavior;
using UnityEngine;
using Modifier = Unity.Behavior.Modifier;
using Unity.Properties;

[Serializable, GeneratePropertyBag]
[NodeDescription(name: "FindPlayer", story: "Repeat While [Player] Is in Sight", category: "Test", id: "fd6b138b2da12770f4c6a46ee617c6c7")]
public partial class FindPlayerModifier : Modifier
{
    [SerializeReference] public BlackboardVariable<GameObject> Player;

    protected override Status OnStart()
    {
        return Status.Running;
    }

    protected override Status OnUpdate()
    {
        return Status.Success;
    }

    protected override void OnEnd()
    {
    }
}

出来上がったノードはこんな感じ

チェーンマークをクリックするとBlackbordに変数が追加されたので、Playerとした。

BlackBordにはBehaviorAgentから設定可能

(複数探したい対象がいるなら指定するのはtagとかでもいいかもしれない)

で、出来上がったものがこちら

扇型の中にプレイヤーが入ると
StartNode で子どものノードを設定して待つ。

ちなみにBlackboardVariableとはBlackBordにアクセスできる変数になる。
このあたりはShaderGraphのパラメーターに定義が似ているので触れば分かるだろうと思う。

ハマりポイントとしては

    [SerializeField]
    public BlackboardVariable<Material> mat;

はプロパティからアクセス可能だが

    [SerializeField]
    private BlackboardVariable<Material> mat;

は表示されない,。
(結局Materialは使わなかったが……)

どうやら private にした場合はGraph側からアクセスは駄目らしい。
後プロパティで Range( 0.0f, 360.0f ) を書いてみたが、どうやらこのアトリビュートは現状動作しないらしい……。

組むコツとして公式サンプルではActionに実行条件を持たせている箇所が少なくないが
正直条件と実行内容は分けたほうがノードが作りやすいのでわけるのをオススメします。

Actionでプレイヤーを追いかける


さっきと同じような要領でスクリプトを追加する。

こちらはプレイヤーの場所を
agent.destination = Target.Value.position;
で渡すだけだ。

近くに寄るまで Status.Running を返しても良かったが、
見失う判定を先程作った親の FindPlayerModifier でやっているので
即座に Status.Success を返すように変更.

Actionで巡回する


Vector3のポジションを順番に巡る処理

巡回ポイントをVector3のポジションを複数登録しておいて順番に巡るだけにしてみた。

これで動作のノードはおしまい

実際にノードを組み立てる


Playerの検索はRayを飛ばしたりしているので、頻繁に処理するのは重いということで以下のように処理を作ってみた。

Try In Order で順番に処理,
Repeat until Failure で子ノードが失敗しない限り繰り返す
Find Player(FindPlayerModifier) でPlayerを探す.

Set Text sate to on →これはエネミーの上に表示する[!]のゲームオブジェクトです。
敵がプレイヤーを見つけているときにだけ[!]を表示させるようにするために追加. Player is Follow(FollowTargetAction ) でプレイヤーを追跡.

プレイヤーが見つけられなかったら次の処理に進み、
Set Text sate to off で[!]を消す
Wait for 0.1 seconds で0.1秒待つ
(毎フレームプレイヤーチェックとかしてたら重そうなので追加)

最後に PatrollPoint Patrol(PatrolAction) で巡回するようにしてみた。

実際に動いているところ


youtu.be

まとめ


以上です。
内部はPlayableGraphで作られているっぽいのでやろうと思えば色々応用が効きそうだなと思いました。
とはいえ、スクリプトを動作確認するたびにコンパイルするのであれば asmdef を切れたほうがコンパイル時間が少なくすむので工夫は必要そうだなと思いました。
まぁ、これまでアセットを使わなければできなかったBehaviorTreeが純正で出来るのは嬉しいですね!
皆様も使ってみてはいかがでしょうか




以上の内容はhttps://orotiyamatano.hatenablog.com/entry/Behaviorより取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14