指定のポイント間を、扇形の視野角でもって、徘徊し、NavMeshで移動。発見時、追尾する例。
using UnityEngine;
using UnityEngine.AI;
namespace DetectSample{
public class EnemyController_Flag : MonoBehaviour
{
public Transform player; // PlayerのTransform
public float viewRadius = 10f; // 視野の半径
public float viewAngle = 45f; // 視野の角度
public LayerMask playerMask; // Playerのレイヤーマスク
public LayerMask obstacleMask; // 障害物のレイヤーマスク
private NavMeshAgent agent;
public Transform[] patrolPoints; // 巡回ポイント
private Vector3 lastKnownPosition; // Playerの最後に認識した位置
private bool isSearching = false;
private float searchTimer = 0f;
public float searchDuration = 5f; // 探索時間
private int currentPatrolIndex = 0;
private bool isResting = false; // 休憩中かどうかのフラグ
public float restDuration = 2f; // 休憩時間
private float restTimer = 0f; // 休憩タイマー
void Start()
{
agent = GetComponent<NavMeshAgent>();
// agent.SetDestination(patrolPoints[currentPatrolIndex].position);
GoToNextPatrolPoint();
}
void Update()
{
// Playerが視界に入っているか判定
if (IsPlayerInSight())
{
Debug.Log("Player detected");
// 視線の範囲を円形にし、距離を2倍に拡大
viewRadius *= 5f;
viewAngle = 360f;
// Playerを追尾する
agent.SetDestination(player.position);
// 最後に見た位置を記憶
lastKnownPosition = player.position;
isSearching = true;
searchTimer = 0f;
}
else if (isSearching)
{
Debug.Log("isSearching");
if (Vector3.Distance(transform.position, lastKnownPosition) < agent.stoppingDistance)
{
// 一定時間、周囲を探索する
searchTimer += Time.deltaTime;
if (searchTimer >= searchDuration)
{
isSearching = false;
// 巡回行動に戻る
GoToNextPatrolPoint();
}
}
else
{
// 最後に見た位置に向かう
agent.SetDestination(lastKnownPosition);
}
}
else if (!isResting && !agent.pathPending && agent.remainingDistance < agent.stoppingDistance)
{
// ポイントに到達したので休憩を開始
isResting = true;
restTimer = 0f;
}
else if (isResting)
{
// 休憩中です
restTimer += Time.deltaTime;
if (restTimer >= restDuration)
{
// 休憩を終了
isResting = false;
GoToNextPatrolPoint();
}
}
else
{
// 元の視野範囲に戻す
viewRadius = 20f;
viewAngle = 90f;
// 巡回行動を行う
Patrol();
}
}
bool IsPlayerInSight()
{
Vector3 directionToPlayer = (player.position - transform.position).normalized;
float distanceToPlayer = Vector3.Distance(transform.position, player.position);
// 視野内にPlayerがいるか確認
if (distanceToPlayer < viewRadius)
{
// transform.forwardを使用して、ローカル座標系での前方向を基準に判定
if (Vector3.Angle(transform.forward, directionToPlayer) < viewAngle / 2)
{
// レイキャストで障害物がないか確認
if (!Physics.Raycast(transform.position, directionToPlayer, distanceToPlayer, obstacleMask))
{
return true; // Playerを発見
}
}
}
return false; // Playerを発見していない
}
void Patrol()
{
if (!agent.pathPending && agent.remainingDistance < agent.stoppingDistance)
{
GoToNextPatrolPoint();
Debug.Log("次の地点へ");
}
}
void GoToNextPatrolPoint()
{
if (patrolPoints.Length == 0) return;
agent.SetDestination(patrolPoints[currentPatrolIndex].position);
currentPatrolIndex = (currentPatrolIndex + 1) % patrolPoints.Length;
}
// 視野の可視化(デバッグ用)
void OnDrawGizmos()
{
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(transform.position, viewRadius);
Vector3 viewAngleA = DirFromAngle(-viewAngle / 2);
Vector3 viewAngleB = DirFromAngle(viewAngle / 2);
Gizmos.DrawLine(transform.position, transform.position + viewAngleA * viewRadius);
Gizmos.DrawLine(transform.position, transform.position + viewAngleB * viewRadius);
}
Vector3 DirFromAngle(float angleInDegrees)
{
// オブジェクトのローカル座標系での前方向を基準にする
return transform.forward * Mathf.Cos(angleInDegrees * Mathf.Deg2Rad) +
transform.right * Mathf.Sin(angleInDegrees * Mathf.Deg2Rad);
}
}
}
上記内容をステートパターンを使用して書いてみた
Stateを継承した実際の行動に関するプログラム
using UnityEngine;
using UnityEngine.AI;
namespace DetectSample
{
public class EnemyController_State : MonoBehaviour
{
public Transform player;
public float viewRadius = 10f;
public float viewAngle = 45f;
public LayerMask playerMask;
public LayerMask obstacleMask{ get; set; }
public NavMeshAgent agent;
public Transform[] patrolPoints;
public float searchDuration = 5f;
public float restDuration = 2f;
public Vector3 lastKnownPosition { get; set; }
public bool isSearching { get; set; } = false;
public float searchTimer { get; set; } = 0f;
public bool isResting { get; set; } = false;
public float restTimer { get; set; } = 0f;
private State currentState;
void Start()
{
agent = GetComponent<NavMeshAgent>();
ChangeState(new PatrolState(this));
}
void Update()
{
currentState.UpdateState();
}
public void ChangeState(State newState)
{
if (currentState != null)
{
currentState.ExitState();
}
currentState = newState;
currentState.EnterState();
}
public bool IsPlayerInSight()
{
Vector3 directionToPlayer = (player.position - transform.position).normalized;
float distanceToPlayer = Vector3.Distance(transform.position, player.position);
if (distanceToPlayer < viewRadius)
{
if (Vector3.Angle(transform.forward, directionToPlayer) < viewAngle / 2)
{
if (!Physics.Raycast(transform.position, directionToPlayer, distanceToPlayer, obstacleMask))
{
return true;
}
}
}
return false;
}
public void SetDestination(Vector3 destination)
{
agent.SetDestination(destination);
}
public bool IsAtDestination()
{
return !agent.pathPending && agent.remainingDistance < agent.stoppingDistance;
}
public void ResetVision()
{
viewRadius = 10f;
viewAngle = 45f;
}
}
}
namespace DetectSample
{
public abstract class State
{
public abstract void EnterState();
public abstract void UpdateState();
public virtual void ExitState() { } // ExitState メソッドを追加
}
}
using UnityEngine;
namespace DetectSample
{
public class SearchState : State
{
private EnemyController_State enemy;
public SearchState(EnemyController_State enemy)
{
this.enemy = enemy;
}
public override void EnterState()
{
enemy.searchTimer = 0f;
enemy.SetDestination(enemy.lastKnownPosition);
}
public override void UpdateState()
{
if (Vector3.Distance(enemy.transform.position, enemy.lastKnownPosition) < enemy.agent.stoppingDistance)
{
enemy.searchTimer += Time.deltaTime;
if (enemy.searchTimer >= enemy.searchDuration)
{
enemy.ChangeState(new PatrolState(enemy));
}
}
else
{
enemy.SetDestination(enemy.lastKnownPosition);
}
}
public override void ExitState()
{
// 探索中のフラグとタイマーをリセット
enemy.isSearching = false;
enemy.searchTimer = 0f;
}
}
}
using UnityEngine;
namespace DetectSample
{
public class ChaseState : State
{
private EnemyController_State enemy;
public ChaseState(EnemyController_State enemy)
{
this.enemy = enemy;
}
public override void EnterState()
{
enemy.viewRadius *= 5f;
enemy.viewAngle = 360f;
}
public override void UpdateState()
{
if (enemy.IsPlayerInSight())
{
enemy.SetDestination(enemy.player.position);
enemy.lastKnownPosition = enemy.player.position;
}
else
{
enemy.ChangeState(new SearchState(enemy));
}
}
public override void ExitState()
{
// 視野範囲を元に戻す
enemy.ResetVision();
}
}
}
using UnityEngine;
namespace DetectSample
{
public class PatrolState : State
{
private EnemyController_State enemy;
private int currentPatrolIndex = 0;
public PatrolState(EnemyController_State enemy)
{
this.enemy = enemy;
}
public override void EnterState()
{
GoToNextPatrolPoint();
}
public override void UpdateState()
{
if (enemy.isResting)
{
enemy.restTimer += Time.deltaTime;
if (enemy.restTimer >= enemy.restDuration)
{
enemy.isResting = false;
GoToNextPatrolPoint();
}
}
else if (enemy.IsAtDestination())
{
enemy.isResting = true;
enemy.restTimer = 0f;
}
}
public override void ExitState()
{
// 休憩中のフラグとタイマーをリセット
enemy.isResting = false;
enemy.restTimer = 0f;
}
private void GoToNextPatrolPoint()
{
if (enemy.patrolPoints.Length == 0) return;
enemy.SetDestination(enemy.patrolPoints[currentPatrolIndex].position);
currentPatrolIndex = (currentPatrolIndex + 1) % enemy.patrolPoints.Length;
}
}
}
using UnityEngine;
namespace DetectSample
{
public class RestState : State
{
private EnemyController_State enemy;
public RestState(EnemyController_State enemy)
{
this.enemy = enemy;
}
public override void EnterState()
{
enemy.restTimer = 0f;
}
public override void UpdateState()
{
enemy.restTimer += Time.deltaTime;
if (enemy.restTimer >= enemy.restDuration)
{
enemy.ChangeState(new PatrolState(enemy));
}
}
public override void ExitState()
{
// 休憩中のフラグとタイマーをリセット
enemy.isResting = false;
enemy.restTimer = 0f;
}
}
}