//tips
新年あけましておめでとうございます。本年もよろしくお願いいたします。
//EnemyAIまわり
Enemyの索敵エリアから外れたら、プレイヤーを追いかけるのをやめるようにスクリプトを変更する。
OnTriggerExitメソッドを追加し、EnemyState.Waitをセットする。
using UnityEngine;
using System.Collections;
public class SearchCharacter : MonoBehaviour
{
private MoveEnemy moveEnemy;
void Start()
{
moveEnemy = GetComponentInParent<MoveEnemy>();
}
void OnTriggerStay(Collider col)
{
// プレイヤーキャラクターを発見
if (col.tag == "Player")
{
// 敵キャラクターの状態を取得
MoveEnemy.EnemyState state = moveEnemy.GetState();
// 敵キャラクターが追いかける状態でなければ追いかける設定に変更
if (state != MoveEnemy.EnemyState.Chase)
{
moveEnemy.SetState(MoveEnemy.EnemyState.Chase, col.transform);
}
}
}
void OnTriggerExit(Collider col)
{
if (col.tag == "Player")
{
moveEnemy.SetState(MoveEnemy.EnemyState.Wait);
}
}
}
スクリプトで目的地を設定し移動させると、障害物にぶつかりながら移動するので、ナビゲーションの機能を使って敵キャラクターをスムーズに移動出来るようにする。
NavMeshAgentという機能を使用する。
まずEnemyに障害物と認識させたいオブジェクトのstaticにチェックをいれ、UnityメニューからWindow/AI/Navigationを選択。
BakeタブでNavigation Staticにチェックされたゲームオブジェクトをベイクする。青色のエリアが表示されるが、Areasのタブで移動コストを別に設定することもでき、歩きづらいエリアなどを意図的に作ることができる。
別のエリアを設定する際には、インスペクタでStaticにチェックを入れ、ヒエラルキーで選択した状態でNavigationウインドウのObjectタブを選択し、Areaを指定する。
次はEnemyにNavMeshAgentコンポーネントを追加する。
このNavMeshAgentの使用によりスクリプトのdirection = (setPosition.GetDestination() - transform.position).normalized;周りも簡素化できるので、そちらも修正する。
navMeshAgent.SetDestination(targetのVector3);で簡単に目的地を設定する事ができる。
navMeshAgent.isStoppedを使うことで、isStoppedのオン・オフする事でエージェントの停止・始動を操作している。velocity = direction * walkSpeed;もスクリプトに記載する必要がなくなった。
下記がNavMeshAgentを導入した後のMoveEnemyスクリプト。
using UnityEngine;
using System.Collections;
using UnityEngine.AI;
public class MoveEnemy : MonoBehaviour
{
// 目的地
private Vector3 destination;
// 歩くスピード
[SerializeField]
private float walkSpeed = 1.0f;
// 速度
private Vector3 velocity;
// 移動方向
private Vector3 direction;
private Rigidbody rigd;
private bool arrived;
private SetPosition setPosition;
// 待ち時間
[SerializeField]
private float waitTime = 1f;
// 経過時間
private float elapsedTime;
// 敵の状態
private EnemyState state;
// 追いかけるキャラクター
private Transform playerTransform;
// エージェント
private NavMeshAgent navMeshAgent;
// 回転スピード
[SerializeField]
private float rotateSpeed = 45f;
public enum EnemyState
{
Walk,
Wait,
Chase
};
void Start()
{
rigd = GetComponent<Rigidbody>(); //プレイヤーのRigidbodyを取得
//destination = new Vector3(4, 0f, 4f);
setPosition = GetComponent<SetPosition>();
setPosition.CreateRandomPosition(); //目的地設定
velocity = Vector3.zero;
arrived = false;
elapsedTime = 0f;
navMeshAgent = GetComponent<NavMeshAgent>();
SetState(EnemyState.Walk);
}
// Update is called once per frame
void Update()
{
// 見回りまたはキャラクターを追いかける状態
if (state == EnemyState.Walk || state == EnemyState.Chase)
{
// キャラクターを追いかける状態であればキャラクターの目的地を再設定
if (state == EnemyState.Chase)
{
setPosition.SetDestination(playerTransform.position);
navMeshAgent.SetDestination(setPosition.GetDestination());
}
//必要か確認
//velocity = Vector3.zero;
//direction = (setPosition.GetDestination() - transform.position).normalized;
//transform.LookAt(new Vector3(setPosition.GetDestination().x, transform.position.y, setPosition.GetDestination().z));
//velocity = direction * walkSpeed;
// 目的地に到着したかどうかの判定
//if (Vector3.Distance(transform.position, setPosition.GetDestination()) < 0.5f)
if (navMeshAgent.remainingDistance < 0.5f)
{
SetState(EnemyState.Wait);
}
}
else if (state == EnemyState.Wait)
{
elapsedTime += Time.deltaTime;
// 待ち時間を越えたら次の目的地を設定
if (elapsedTime > waitTime)
{
SetState(EnemyState.Walk);
}
}
//必要か確認
//velocity.y += Physics.gravity.y * Time.deltaTime;
//rigd.velocity = velocity;
}
/*
void Update()
{
if (!arrived)
{
velocity = Vector3.zero;
direction = (destination - transform.position).normalized;
transform.LookAt(new Vector3(destination.x, transform.position.y, destination.z));
velocity = direction * walkSpeed;
Debug.Log(destination);
//velocity.y += Physics.gravity.y * Time.deltaTime;
rigd.velocity = velocity;
if (Vector3.Distance(transform.position, destination) < 0.5f)
{
arrived = true;
}
}
else
{
elapsedTime += Time.deltaTime;
// 待ち時間を越えたら次の目的地を設定
if (elapsedTime > waitTime)
{
setPosition.CreateRandomPosition();
destination = setPosition.GetDestination();
arrived = false;
elapsedTime = 0f;
}
Debug.Log(elapsedTime);
}
}
*/
// 敵キャラクターの状態変更メソッド
public void SetState(EnemyState tempState, Transform targetObj = null)
{
if (tempState == EnemyState.Walk)
{
arrived = false;
elapsedTime = 0f;
state = tempState;
setPosition.CreateRandomPosition();
navMeshAgent.SetDestination(setPosition.GetDestination());
navMeshAgent.isStopped = false;
}
else if (tempState == EnemyState.Chase)
{
state = tempState;
// 待機状態から追いかける場合もあるのでOff
arrived = false;
// 追いかける対象をセット
playerTransform = targetObj;
navMeshAgent.SetDestination(playerTransform.position);
navMeshAgent.isStopped = false;
}
else if (tempState == EnemyState.Wait)
{
elapsedTime = 0f;
state = tempState;
arrived = true;
velocity = Vector3.zero;
}
}
// 敵キャラクターの状態取得メソッド
public EnemyState GetState()
{
return state;
}
}
動くゲームオブジェクトをリアルタイムに障害物として認識させる機能NavMeshObstacleコンポーネントも追加して、よりEnemyの動きをリアルにしていく。
X軸方向に行き来するCubeを作成し、Nav Mesh Obstacleコンポーネントを追加する。
Carve Only Stationaryのチェックを外すとゲームオブジェクトが動いていても障害認識が有効に働くので外しておく。
動く障害物には下記のスクリプトをアタッチした。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SimpleCubeMove : MonoBehaviour
{
private Vector3 basePos;
void Start()
{
basePos = transform.position;
}
void Update()
{
transform.position = basePos + new Vector3(Mathf.Sin(Time.time) * 3f, 0f, 0f);
}
}