NavMeshを使用した際のアニメーションの設定【Unity】

NavMeshを使用して、ある程度敵の追跡処理が実装できたところで、攻撃用のアニメーションを実装してプレイヤーを攻撃するようにしたいと思います。久しぶりだったとはいえ、大して難しくない処理を実装するのに大分手間取ってしまいました・・・。

NavMeshを使用した際のアニメーションの設定【Unity】

以前から使用している敵モデルには、既に基本的なアニメーションが用意されているので、この中から「Idle」「Move」「Attack」に関するアニメーションを使います。

アニメーションの設定を行う

まずは、適当にAnimatorControllerを作成します。名前はオブジェクトを参考に好きな名前で。

対象となるオブジェクトにアタッチしたら、アニメーションコントローラーを開きます。

アニメーターウィンドウ

レイヤーは画面のようにします。「Idle」「Move」「Attack」のエンティティを作成したら画像のように繋ぎます。

アタックに関しては、「Any State」から繋ぐようにしました。これにより、どのステートからでもAttackに移行することが可能です。

アタックのアニメーションが再生された後は、他のアニメーションに遷移する必要があるのでAttackからExitにTransitionを繋ぐようにしておきます。

モーション設定

各アニメーションのモーションを適宜設定します。上はIdleの場合ですね。Moveは移動時、Attackは攻撃時のアニメーションを設定しましょう。

続いて、パラメーターの設定をします。「Parameters」からTrigger型の「Attack」とfloat型の「Move」を作成します。

Conditionsの設定

AnyState -> AttackのTransitionからConditionsを「Attack」にします。

IdleからMoveのConditions

Idle -> MoveではConditionsを左から「MoveSpeed」「Greate」「0.01」にします。これで、MoveSpeedが0.01以上になったらMoveアニメーションになります。

Move -> Idleは、Greateを「Less」にしたらその他は上と同じ設定でOKです。

Statusmanagerスクリプトの作成

プレイヤーや敵など移動するオブジェクトの状態を管理するスクリプトを作成します。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public abstract class Statusmanager : MonoBehaviour
{
protected enum State
{
Normal,
Attack
}
public bool IsMove => State.Normal == state;
public bool IsAttack => State.Normal == state;
protected Animator animator;
protected State state = State.Normal;
protected virtual void Start()
{
animator = GetComponentInChildren<Animator>();
}
public void GoToAttack()
{
if (!IsAttack) return;
state = State.Attack;
animator.SetTrigger("Attack");
}
public void GoToNormal()
{
state = State.Normal;
}
}
view raw Statusmanager hosted with ❤ by GitHub

後々敵を倒した際の処理などを実装することを考えて、共通化しておきます。

現時点では、攻撃するかしないかだけの処理ですね。

EnemyStatusスクリプトの作成

次に、敵のステータスを管理するスクリプトを作成します。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class EnemyStatus : Statusmanager
{
private NavMeshAgent agent;
protected override void Start()
{
base.Start();
agent = GetComponent<NavMeshAgent>();
}
private void Update()
{
animator.SetFloat("MoveSpeed", agent.velocity.magnitude);
}
}
view raw EnemyStatus hosted with ❤ by GitHub

agent.velocity.magnitudeで速度のベクトルを取得して、値によって「Idle」「Move」いずれかのアニメーションが再生されるようにします。

Attackスクリプトの作成

続いて、攻撃用のスクリプトを作ります。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Attack : MonoBehaviour
{
public float attackspan = 0.5f;
public Collider attackCollider;
private Statusmanager status;
// Start is called before the first frame update
private void Start()
{
status = GetComponent<Statusmanager>();
}
public void CheckAttack()
{
if (!status.IsAttack) return;
status.GoToAttack();
}
public void AttackStart()
{
attackCollider.enabled = true;
}
public void AttackEnd()
{
attackCollider.enabled = false;
StartCoroutine(Attackspan());
}
private IEnumerator Attackspan()
{
yield return new WaitForSeconds(attackspan);
status.GoToNormal();
}
}
view raw Attack hosted with ❤ by GitHub

この後攻撃アニメーションを再生して、任意のタイミングでAttackStartとAttackEndメソッドを呼び出して、当たり判定を実装したいと思います。

SlimeControllerスクリプトを修正する

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class SlimeController : MonoBehaviour
{
private NavMeshAgent agent;
public GameObject damageeffect;
private RaycastHit hit;
private EnemyStatus status;
private Attack at;
void Start()
{
agent = GetComponent<NavMeshAgent>();
status = GetComponent<EnemyStatus>();
at = GetComponent<Attack>();
}
void Update()
{
}
private void OnTriggerStay(Collider other)
{
if (other.CompareTag("Player"))
{
if (!status.IsMove)
{
agent.isStopped = true;
return;
}
GameObject Target = GameObject.Find("SDunitychan");
var diff = Target.transform.position - transform.position;
var distance = diff.magnitude;
var direction = diff.normalized;
if(distance < 1.0f)
{
at.CheckAttack();
}
if(Physics.Raycast(transform.position, direction, out hit, distance))
{
if(hit.transform.gameObject == Target)
{
agent.isStopped = false;
agent.destination = Target.transform.position;
}
else
{
agent.isStopped = true;
}
}
}
}
private void OnCollisionEnter(Collision other)
{
if (other.gameObject.tag == "Player")
{
foreach(ContactPoint point in other.contacts)
{
GameObject effect = Instantiate(damageeffect) as GameObject;
effect.transform.position = (Vector3)point.point;
}
}
}
}
view raw Sli hosted with ❤ by GitHub

移動可能かどうかをチェックして、敵とプレイヤーの距離が1.0f以下になったら、攻撃アニメーションを作成するようにします。

アニメーションにイベントを設定して任意のタイミングでメソッドを実行する

アニメーションの途中で、メソッドを実行することが出来るので、攻撃アニメーションを再生してみて、とあるタイミングで指定のメソッドを実行したい場合に便利です。

例えば、私の使用しているゴーストの場合のアタックアニメーションはこんな感じです。

完全に頭突きですよね。この場合は頭を突き出した際に当たり判定を発生させたほうが良いので、そのタイミングで指定したメソッドを呼び出すようにします。

AnimationのRead-Onlyを解除する

実行イベントを追加するためには、アニメーションのRead-Only(読み込み専用)を解除する必要があるのでまずはその作業から行います。

Read-Onlyの解除

編集する対象はアタックアニメーションなので、「ghost_attack」を複製して見分けが付きやすい名前に変更します。これで、FBXが切り離されて編集出来るようにはるはずです。

Attackアニメーションに実行イベントを追加する

対象のオブジェクトを選択した状態で「Window」→「Animation」→「Animation」を選択します。

アニメーションの編集

左上から、先ほど複製したアニメーションを選択します。(Read-Onlyが解除されている)

アニメーションに実行イベントを付加

指定した秒数の下で右クリックして「Add Animation Event」を選択します。私の場合は0.07秒辺りが丁度よさそうでした。

続いて、Functionから「AttackStart()」を選択します。

同様に、当たり判定が終了する位置も設定しましょう。私は0.12秒にしました。Functionは「AttackEnd()」を選択するようにします。

敵に当たり判定用コライダを作成する

敵を選択して空のオブジェクトを作成して、当たり判定用のBox Colliderをアタッチします。

Is Triggerにはチェックを入れておきます。

当たり判定用コライダ

コライダの位置は、頭突きしたときの事を考えて敵の正面からやや奥にしています。

「EnemyStatus」と「Attack」スクリプトを敵にアタッチしてインスペクタから今作成したAttackColliderを設定したらゲームを実行してみます。

こんな感じですね。

今回は、NavMeshを使用した際のアニメーションに関する処理の実装でした。

次回はダメージ処理ですかね。