e.blog

主にUnity/UE周りのことについてまとめていきます

NavMeshAgentの挙動を手動でアップデートする

概要

NavMeshを使うことで、Unityでは簡単に目的までのパス(経路)を計算することができます。

docs.unity3d.com

経路探索して移動させたいオブジェクトに「NavMeshAgent」をアタッチし、「Window > Navigation」メニューで表示されるナビゲーション設定にて、経路構築を行うことですぐに「目的地への移動」を実現させることができます。

しかし、例えばアニメーションと組み合わせたり、なにかしらの自身のロジックと組み合わせて利用したいというケースもあると思います。
(例えばAIを実装して、AIに移動を行ってもらいたいが位置や回転のアップデートは自分のタイミングで行いたい場合など)

そういう場合には勝手に移動されてしまうと問題です。
そこで、手動で位置と回転をアップデートする方法を書いておこうと思います。
(ちなみにドキュメントにも記載があるので、そちらも合わせて見てみるといいかもしれません)

docs.unity3d.com

準備

さて、基本的にはNavMeshAgentをアタッチし、目的地を設定した段階で自動的に位置の移動が開始されます。
そのため、いくつかのスクリプトを記述して自動更新を止める必要があります。

自動更新を止めるスクリプトを書く

具体的には以下のようにプロパティを設定します。

void Awake ()
{
    _agent = GetComponent<NavMeshAgent>();

    // updateを自動で行わないように設定する
    _agent.updatePosition = false;
    _agent.updateRotation = false;

    // 目的地はインスペクタなどで事前に設定しておく
    _agent.SetDestination(_target.position);
}

これで、NavMeshAgentによるゲームオブジェクトの自動更新が停止します。
(ただし、見た目の更新が停止しているだけで、内部的な経路の探索と位置は常にアップデートされていきます)

更新を手動反映させる

自動反映をとめたので、今のままだとゲームオブジェクトはまったく動かなくなります。
今回の目的はこれを手動で更新する方法です。

位置に関してはnextPositionに、アップデート間隔で計算された次の位置が保持されているので、これをそのまま設定してやれば位置の更新ができます。
しかし回転についてはいくつか自分で計算しないとならないのでそれを行います。

最終的なコードは以下のようになります。
計算自体は軸と角度を求めるだけです。

void Update ()
{
    // 次の位置への方向を求める
    var dir = _agent.nextPosition - transform.position;

    // 方向と現在の前方との角度を計算(スムーズに回転するように係数を掛ける)
    float smooth = Mathf.Min(1.0f, Time.deltaTime / 0.15f);
    var angle = Mathf.Acos(Vector3.Dot(transform.forward, dir.normalized)) * Mathf.Rad2Deg * smooth;

    // 回転軸を計算
    var axis = Vector3.Cross(transform.forward, dir);

    // 回転の更新
    var rot = Quaternion.AngleAxis(angle, axis);
    transform.forward = rot * transform.forward;

    // 位置の更新
    transform.position = _agent.nextPosition;
}

ShaderLabについてメモ

もっぱらQiitaで記事を書いていましたが、そろそろUnityの話題に絞ったブログを作りたくなったので、過去に少しだけ使っていたこのブログをまっさらにしました。 今後のUnityの記事はここにまとめていこうと思います。

ということで、第一回目はメモとして残しておいたやつを投稿しておこうと思いますw

Propertiesに使える属性

Propertiesにはいくつかの属性を指定することができるようになっています。 指定できる属性は以下の通り。(詳細はドキュメントをご覧ください)

  • [HideInInspector] ... マテリアルインスペクタでプロパティを非表示にする
  • [NoScaleOffset] ... 属性のテクスチャに関して、マテリアルインスペクタの texture tiling / offset を非表示にする
  • [Normal] ... テクスチャが法線マップであることを示す
  • [HDR] ... テクスチャが High Dynamic Range (HDR) テクスチャであることを示す
  • [Gamma] ... Float / Vector プロパティがUIでsRGB値で指定されており、使用するカラースペースに応じて変換が必要な可能性があることを示す
  • [PerRenderData] ... テクスチャは MaterialPropertyBlock の形式の各レンダラーごとのデータであることを示す(マテリアルインスペクタでは、このプロパティのテクスチャ部分のUIが変わる)
  • [KeywordEnum(A,B,C)] ... Enumとして値を設定できるようにする

バリアントについて(#pragma multi_compile A B)

バリアントを定義し、マルチコンパイルすることで、複数の状態に応じたシェーダをコンパイルすることができるようです。 (ドキュメントはこちら

なお、詳細についてはこちらの記事(UnityのShader Variantについて調べてみた)が分かりやすいです。

バリアントについては以下のように #pragma を用いて宣言します。

#pragma multi_compile __ A B C

ここで、__ はどれも指定されていない場合の処理もコンパイルしてほしいことをコンパイラに伝えます。

使い方は以下のようになります。

fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;

#ifdef A
c.rgb += fixed3(0.5, 0.0, 0.0);
#elif B
c.rgb += fixed3(0.0, 0.5, 0.0);
#elif C
c.rgb += fixed3(0.0, 0.0, 0.5);
#endif

こうしておくことで、それぞれのバリアントが定義されているときように、すべての状態のシェーダがコンパイルされます。(なので マルチ なんですね)

[KeywordEnum()]を応用する

[KeywordEnum()] を応用することで、このバリアントをインスペクタから設定することも可能になります。

※ 注意点として、定義するバリアントはすべて大文字である必要があります。(小文字を含めるとうまく動きません)

各Pass共通の処理をまとめておく

以下のように CGINCLUDE を利用して、各Passで使う共通の処理などをまとめておくことができます。 (もし頂点シェーダの挙動はどれも共通で、フラグメントシェーダだけ違う、という場合も、ここに頂点シェーダを書いておいて使いまわすことができます)

Shader "Custom/AnyShader" {
    Properties {
        // ...
    }

    CGINCLUDE
    #include "UnityCG.cginc"
    
    sampler2D _MainTex;

    struct v2f {
        float4 pos : SV_POSITION;
        float2 uv : TEXCOORD0;
    };

    v2f vert(appdata_base v) {
        v2f o;
        o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
        o.uv = v.normal;
        return o;
    }
    ENDCG

    SubShader {
        Pass {
            // ...
            #pragma vertex vert
            // ...
        }
    }
}

セマンティクス

入力・出力に意味づけするもの。SV_POSITION などと指定しているのがそれ。

  • COLOR
  • SV_POSITION ... プロジェクション後の座標
  • POSITION ... 空間中の座標
  • TEXCOORD0
  • NORMAL ... 法線
  • TANGENT ... 接線
  • WPOS ... フラグメントシェーダで現在扱っているピクセルの座標