【Unity3D】フィールド上の敵を倒せるようにする

Unityちゃんに剣を振らせるように出来たので、フィールド上にいる敵を倒せるようにしたいと思います。という訳で、これまで作成してきたアニメーターやスクリプトを修正していきます。

【Unity3D】フィールド上の敵を倒せるようにする

Unityちゃんのアニメーターを修正する

プレイヤーと敵それぞれにHPを持たせるようにしますが、敵のHPが0になったらDestroyを使ってその場で消すようにします。

一方Unityちゃんが倒された場合は、対応するアニメーションを再生したいと思います。

Unityちゃんのアニメーター

Dieアニメーションを作成してAny Stateから繋ぐようにします。パラメーターはTrigger型ですね。

Motionは、Unityちゃんがダウンしているようなアニメーションを設定するようにしてください。

Statusmanagerスクリプトの修正

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public abstract class Statusmanager : MonoBehaviour
{
protected enum State
{
Normal,
Attack,
Die
}
public bool IsMove => State.Normal == state;
public bool IsAttack => State.Normal == state;
public bool IsDie => State.Die == state;
protected Animator animator;
protected State state = State.Normal;
[SerializeField] private int Maxlife;
[SerializeField] private int life;
protected virtual void Start()
{
life = Maxlife;
animator = GetComponentInChildren<Animator>();
}
public void GoToAttack()
{
if (!IsAttack) return;//Attackならreturn
state = State.Attack;
animator.SetTrigger("Attack");
}
public void GoToNormal()
{
state = State.Normal;
}
public int Life()
{
return life;
}
public void Damage(int damage)
{
if (state == State.Die) return;
life -= damage;
if (life > 0) return;
state = State.Die;
animator.SetTrigger("Die");
Die();
}
protected virtual void Die()
{
}
}
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;
private SlimeController sc;
protected override void Start()
{
base.Start();
sc = GetComponent<SlimeController>();
agent = GetComponent<NavMeshAgent>();
}
private void Update()
{
animator.SetFloat("MoveSpeed", agent.velocity.magnitude);
}
protected override void Die()
{
base.Die();
Destroy(gameObject);
}
}
view raw EnemyStatus hosted with ❤ by GitHub

敵が倒された際の処理を実装しています。

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>();
attackCollider.enabled = false;
}
private void Update()
{
}
public void CheckAttack()
{
if (!status.IsAttack) return;
status.GoToAttack();
}
public void AttackStart()//当たり判定用コライダを発動
{
attackCollider.enabled = true;
}
public void HitObject(Collider collider)
{
var obj = collider.GetComponent<Statusmanager>();
if (obj == null) return;
obj.Damage(1);
}
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

このスクリプト内に、OnTriggerEnterメソッドを作ってダメージ計算を行おうとしましたが、敵オブジェクトには、サーチ用と当たり判定用のコライダを両方アタッチしているため、プレイヤーが敵の追跡範囲内に入った時点でダメージを受けてしまいます。

また、ゲーム開始時に当たり判定用のコライダを無効にしておきます。

そこで、UnityEventを使って指定したタイミングで、HitObjectメソッドを実行するようにします。

SlimeControllerスクリプトの修正

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class SlimeController : MonoBehaviour
{
//public Transform target;
private NavMeshAgent agent;
//public float speed = 0.01f;
public GameObject damageeffect;
private RaycastHit hit;
//Vector3 SlimePos;
//Rigidbody rigid;
private EnemyStatus status;
private Attack at;
void Start()
{
// SlimePos = transform.position;
// rigid = GetComponent<Rigidbody>();
agent = GetComponent<NavMeshAgent>();
status = GetComponent<EnemyStatus>();
at = GetComponent<Attack>();
}
void Update()
{
// transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(target.position - transform.position), 0.3f);
// transform.position += transform.forward * this.speed;
// rigid.MovePosition(transform.position + transform.forward * Time.deltaTime);
// transform.rotation = Quaternion.LookRotation(transform.position - SlimePos);
// SlimePos = transform.position;
//transform.rotation = Quaternion.LookRotation(new Vector3(0, 90, 0));
}
private void OnTriggerStay(Collider other)
{
if (other.CompareTag("Player"))
{
if (!status.IsMove)//Normalじゃない
{
if(!status.IsDie)
{
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)
{
if (!status.IsDie)
{
agent.isStopped = false;
agent.destination = Target.transform.position;
}
}
else
{
if (!status.IsDie)
{
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 SlimeController hosted with ❤ by GitHub

敵を倒した際に「”Stop” can only be called on an active agent that has been placed on a NavMesh.」というエラーが表示されるのでスクリプトを修正しました。

恐らく、agentに関する処理を無効にすることで良いのかなと思います。敵が倒されていないならという条件を付与することでこのエラーは解消されました。

HitAttackDirectorスクリプトを作成する

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using System;
public class HitAttackDirector : MonoBehaviour
{
[SerializeField] private EventTrigger onTriggerEnter = new EventTrigger();
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
private void OnTriggerEnter(Collider other)
{
onTriggerEnter.Invoke(other);
}
[Serializable]
public class EventTrigger : UnityEvent<Collider>
{
}
}

OnTriggerEnterで実行されるメソッドを設定するためのスクリプトですね。

保存したらこのスクリプトを、敵の子オブジェクトである「AttackCollider」とUnityちゃんの子オブジェクト「PlayerAttackCollider」にそれぞれアタッチします。

UnityEventの設定

これは敵の例ですが、On Trigger Enter(Collider)の下にある「+」ボタンをクリックして敵オブジェクトを左下の枠にドラッグ&ドロップします。

続いて、実行したいメソッドを指定します。今回の場合はAttackスクリプトのHitObjectを実行したいので「Attack」→「HitObject」を選択します。同様にUnityちゃん側も設定します。

レイヤーの設定

プレイヤーは敵のみを攻撃出来るように、また敵もプレイヤーのみを攻撃できるようにするためレイヤーの設定を行います。

インスペクターの右上にある「Layer」→「Add Layer」をクリックします。

新規レイヤーの作成

画像のようにレイヤーを4つ作成します。

SDUnitychan → 「Player」

PlayerAttackCollider → 「PlayerCollider」

敵オブジェクト → 「Enemy」

AttackCollider → 「EnemyCollider」

となるようにに各オブジェクトに対してレイヤーを設定します。

続いて「Edit」→「Project Settings…」→「Physics」を開きます。

衝突するレイヤーの設定

下にたくさんのチェックボックスが表示されると思うので、衝突させたいレイヤー同士にチェックを入れるようにします。

PlayerColliderはEnemyのみとして、EnemyColliderはPlayerとのみ衝突するようにします。

プレイヤーと敵のインスペクターからMaxlifeに適当に数字を入力してゲームを実行してみましょう。

相手のlifeが0になったらこのように消えるはずです。

インスペクタから攻撃時のlifeの減り方をチェックしてみると良いと思います。

※追記

このままだと、敵のサーチ用コライダ内でUnityちゃんが振った場合にも敵のHPが削られてしまいます。ということで、敵にアタッチしているサーチ用コライダを子オブジェクトを作成してそちらにアタッチさせたいと思います。

コライダの修正

敵の子オブジェクトとして空のオブジェクト「Search」を作成します。後はその中にSphere Colliderを作ればOKですね。これで敵のサーチ内で剣を振ってもダメージを与えることはなくなります。