敵を動かす
1, PlayerにTagを設定する
2, AIControllerを作成し、Enemyにアタッチする
3, 下記スクリプトを書く
AIController
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using RPG.Combat;
namespace RPG.Control
{
public class AIController : MonoBehaviour
{
[SerializeField] float chaseDistance = 5f;
Fighter fighter;
GameObject player;
private void Start()
{
fighter = GetComponent<Fighter>();
player = GameObject.FindWithTag("Player");
}
private void Update()
{
if (InAttackRangeOfPlayer() && fighter.CanAttack(player))
{
fighter.Attack(player);
}
else
{
fighter.Cancel();
}
}
private bool InAttackRangeOfPlayer()
{
float distanceToPlayer = Vector3.Distance(player.transform.position, transform.position);
return distanceToPlayer < chaseDistance;
}
}
}
PlayerController
using UnityEngine;
using System.Collections;
using RPG.Movement;
using System;
using RPG.Combat;
namespace RPG.control
{
public class PlayerController : MonoBehaviour
{
private void Update()
{
if (InteractWithCombat()) return;
if (InteractWithMovement()) return;
Debug.Log("Nothing to do");
}
private bool InteractWithCombat()
{
RaycastHit[] hits = Physics.RaycastAll(GetMouseRay());
foreach (RaycastHit hit in hits)
{
CombatTarget target = hit.transform.GetComponent<CombatTarget>();
if (target == null) continue; //追加
GameObject targetGameObject = target.gameObject; //追加
if (!GetComponent<Fighter>().CanAttack(target.gameObject)) //target.gameObjectにする
{
continue;
}
if (Input.GetMouseButtonDown(0))
{
GetComponent<Fighter>().Attack(target.gameObject); //target.gameObjectにする
}
return true;
}
return false;
}
private bool InteractWithMovement()
{
RaycastHit hit;
bool hasHit = Physics.Raycast(GetMouseRay(), out hit);
if (hasHit)
{
if (Input.GetMouseButtonDown(0))
{
GetComponent<Mover>().StartMoveAction(hit.point);
}
return true;
}
return false;
}
private static Ray GetMouseRay()
{
return Camera.main.ScreenPointToRay(Input.mousePosition);
}
}
}
Fighter
using UnityEngine;
using RPG.Movement;
using RPG.Core;
namespace RPG.Combat
{
public class Fighter : MonoBehaviour, IAction
{
[SerializeField] float weaponRange = 2f;
[SerializeField] float timeBetweenAttacks = 1f;
[SerializeField] float weaponDamage = 5f;
Health target;
float timeSinceLastAttack = 0;
private void Update()
{
timeSinceLastAttack += Time.deltaTime;
if (target == null) return;
if (target.IsDead()) return;
if (!GetIsInRange())
{
GetComponent<Mover>().MoveTo(target.transform.position);
}
else
{
GetComponent<Mover>().Cancel();
AttackBehaviour();
}
}
private void AttackBehaviour()
{
transform.LookAt(target.transform);
if (timeSinceLastAttack > timeBetweenAttacks)
{
TriggerAttack();
timeSinceLastAttack = 0;
}
}
private void TriggerAttack()
{
GetComponent<Animator>().ResetTrigger("stopAttack");
GetComponent<Animator>().SetTrigger("attack");
}
void Hit()
{
if(target == null) { return; }
target.TakeDamage(weaponDamage);
}
private bool GetIsInRange()
{
return Vector3.Distance(transform.position, target.transform.position) < weaponRange; //追加
}
public bool CanAttack(GameObject combatTarget) //gameObjectに変更
{
if (combatTarget == null) { return false; }
Health targetToTest = combatTarget.GetComponent<Health>();
return targetToTest != null && !targetToTest.IsDead();
}
public void Attack(GameObject combatTarget) //gameObjectに変更
{
GetComponent<ActionScheduler>().StartAction(this);
target = combatTarget.GetComponent<Health>();
}
public void Cancel()
{
StopAttack();
target = null;
}
private void StopAttack()
{
GetComponent<Animator>().ResetTrigger("attack");
GetComponent<Animator>().SetTrigger("stopAttack");
}
}
}
Playerが死亡した後に動けるバグ修正
2, 各スクリプトにコードを追加
PlayerController
using UnityEngine;
using System.Collections;
using RPG.Movement;
using System;
using RPG.Combat;
using RPG.Core; //追加
namespace RPG.control
{
public class PlayerController : MonoBehaviour
{
Health health; //追加
//追加
private void Start()
{
health = GetComponent<Health>();
}
private void Update()
{
if (health.IsDead()) return; //追加
if (InteractWithCombat()) return;
if (InteractWithMovement()) return;
Debug.Log("Nothing to do");
}
private bool InteractWithCombat()
{
RaycastHit[] hits = Physics.RaycastAll(GetMouseRay());
foreach (RaycastHit hit in hits)
{
CombatTarget target = hit.transform.GetComponent<CombatTarget>();
if (target == null) continue;
GameObject targetGameObject = target.gameObject;
if (!GetComponent<Fighter>().CanAttack(target.gameObject))
{
continue;
}
if (Input.GetMouseButtonDown(0))
{
GetComponent<Fighter>().Attack(target.gameObject);
}
return true;
}
return false;
}
private bool InteractWithMovement()
{
RaycastHit hit;
bool hasHit = Physics.Raycast(GetMouseRay(), out hit);
if (hasHit)
{
if (Input.GetMouseButtonDown(0))
{
GetComponent<Mover>().StartMoveAction(hit.point);
}
return true;
}
return false;
}
private static Ray GetMouseRay()
{
return Camera.main.ScreenPointToRay(Input.mousePosition);
}
}
}
Fighter
using UnityEngine;
using RPG.Movement;
using RPG.Core;
namespace RPG.Combat
{
public class Fighter : MonoBehaviour, IAction
{
[SerializeField] float weaponRange = 2f;
[SerializeField] float timeBetweenAttacks = 1f;
[SerializeField] float weaponDamage = 5f;
Health target;
float timeSinceLastAttack = Mathf.Infinity; //変更
private void Update()
{
timeSinceLastAttack += Time.deltaTime;
if (target == null) return;
if (target.IsDead()) return;
if (!GetIsInRange())
{
GetComponent<Mover>().MoveTo(target.transform.position);
}
else
{
GetComponent<Mover>().Cancel();
AttackBehaviour();
}
}
AIController
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using RPG.Combat;
using RPG.Core; //追加
namespace RPG.Control
{
public class AIController : MonoBehaviour
{
[SerializeField] float chaseDistance = 5f;
Fighter fighter;
Health health; //追加
GameObject player;
private void Start()
{
fighter = GetComponent<Fighter>();
health = GetComponent<Health>(); //追加
player = GameObject.FindWithTag("Player");
}
private void Update()
{
if (health.IsDead()) return; //追加
if (InAttackRangeOfPlayer() && fighter.CanAttack(player))
{
fighter.Attack(player);
}
else
{
fighter.Cancel();
}
}
private bool InAttackRangeOfPlayer()
{
float distanceToPlayer = Vector3.Distance(player.transform.position, transform.position);
return distanceToPlayer < chaseDistance;
}
}
}
Health
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace RPG.Core
{
public class Health : MonoBehaviour
{
[SerializeField] float healthPoints = 100f;
bool isDead = false;
public bool IsDead() //追加
{
return isDead;
}
public void TakeDamage(float damage)
{
healthPoints = Mathf.Max(healthPoints - damage, 0);
if (healthPoints == 0)
{
Die();
}
}
private void Die()
{
if (isDead) return;
isDead = true;
GetComponent<Animator>().SetTrigger("die");
GetComponent<ActionScheduler>().CancelCurrentAction(); //追加
}
}
}
Combat
using UnityEngine;
using System.Collections;
using RPG.Core; //追加
namespace RPG.Combat
{
[RequireComponent(typeof(Health))]
public class CombatTarget : MonoBehaviour
{
}
}
ActionScheduler
using UnityEngine;
using System.Collections;
namespace RPG.Core
{
public class ActionScheduler : MonoBehaviour
{
IAction currentAction;
public void StartAction(IAction action)
{
if (currentAction == action) return;
if (currentAction != null)
{
currentAction.Cancel();
}
currentAction = action;
}
//追加
public void CancelCurrentAction()
{
StartAction(null);
}
}
}
Mover
using System.Collections;
using System.Collections.Generic;
using RPG.Core;
using UnityEngine;
using UnityEngine.AI;
namespace RPG.Movement
{
public class Mover : MonoBehaviour, IAction
{
[SerializeField] Transform target;
NavMeshAgent navMeshAgent;
Health health; //追加
private void Start()
{
navMeshAgent = GetComponent<NavMeshAgent>();
health = GetComponent<Health>(); //追加
}
void Update()
{
navMeshAgent.enabled = !health.IsDead(); //追加
UpdateAnimator();
}
public void StartMoveAction(Vector3 destination)
{
GetComponent<ActionScheduler>().StartAction(this);
MoveTo(destination);
}
public void MoveTo(Vector3 destination)
{
navMeshAgent.destination = destination;
navMeshAgent.isStopped = false;
}
public void Cancel()
{
navMeshAgent.isStopped = true;
}
private void UpdateAnimator()
{
Vector3 velocity = navMeshAgent.velocity;
Vector3 localVelocity = transform.InverseTransformDirection(velocity);
float speed = localVelocity.z;
GetComponent<Animator>().SetFloat("fowardSpeed", speed);
}
}
}
敵の動き出す範囲をGizmoで視覚化する
1, AIControllerに下記コードを追加
AIController
//Calld by Unity
private void OnDrawGizmosSelected()
{
Gizmos.color = Color.blue;
Gizmos.DrawWireSphere(transform.position, chaseDistance);
}
Gizmosの範囲を外れたら元の位置まで戻らせる
1, 下記のコードを追加
AIController
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using RPG.Combat;
using RPG.Core;
using RPG.Movement; //追加
namespace RPG.Control
{
public class AIController : MonoBehaviour
{
[SerializeField] float chaseDistance = 5f;
Fighter fighter;
Health health;
Mover mover; //追加
GameObject player;
Vector3 guardPosition; //追加
private void Start()
{
fighter = GetComponent<Fighter>();
health = GetComponent<Health>();
mover = GetComponent<Mover>(); //追加
player = GameObject.FindWithTag("Player");
guardPosition = transform.position; //追加
}
private void Update()
{
if (health.IsDead()) return;
if (InAttackRangeOfPlayer() && fighter.CanAttack(player))
{
fighter.Attack(player);
}
else
{
mover.StartMoveAction(guardPosition); //追加
}
}
private bool InAttackRangeOfPlayer()
{
float distanceToPlayer = Vector3.Distance(player.transform.position, transform.position);
return distanceToPlayer < chaseDistance;
}
private void OnDrawGizmosSelected()
{
Gizmos.color = Color.blue;
Gizmos.DrawWireSphere(transform.position, chaseDistance);
}
}
}
Gizmo範囲を超えると数秒間立ち止まらせる
1, 下記コードを追加する
AIController
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using RPG.Combat;
using RPG.Core;
using RPG.Movement;
namespace RPG.Control
{
public class AIController : MonoBehaviour
{
[SerializeField] float chaseDistance = 5f;
[SerializeField] float suspicionTime = 3f; // 追加
Fighter fighter;
Health health;
Mover mover;
GameObject player;
Vector3 guardPosition;
float timeSinceLastSawPlayer = Mathf.Infinity; //追加
private void Start()
{
fighter = GetComponent<Fighter>();
health = GetComponent<Health>();
mover = GetComponent<Mover>();
player = GameObject.FindWithTag("Player");
guardPosition = transform.position;
}
private void Update()
{
if (health.IsDead()) return;
if (InAttackRangeOfPlayer() && fighter.CanAttack(player))
{
timeSinceLastSawPlayer = 0; //追加
AttackBehaviour();
}
// 追加
else if (timeSinceLastSawPlayer < suspicionTime)
{
//Suspicion state
SuspicionBehaviour();
}
else
{
GuardBehaviour();
}
timeSinceLastSawPlayer += Time.deltaTime; //追加
}
//追加
private void GuardBehaviour()
{
mover.StartMoveAction(guardPosition);
}
//追加
private void SuspicionBehaviour()
{
GetComponent<ActionScheduler>().CancelCurrentAction();
}
//追加
private void AttackBehaviour()
{
fighter.Attack(player);
}
private bool InAttackRangeOfPlayer()
{
float distanceToPlayer = Vector3.Distance(player.transform.position, transform.position);
return distanceToPlayer < chaseDistance;
}
private void OnDrawGizmosSelected()
{
Gizmos.color = Color.blue;
Gizmos.DrawWireSphere(transform.position, chaseDistance);
}
}
}
敵の歩く道を視覚化する
1, ヒエラルキーでPatrolPathを作成し、WayPort4つ作成して配置する
2, ControlフォルダにPatrolPathスクリプトを作成し、アタッチする
3, 下記コードを記述する
PatrolPath
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace RPG.Control
{
public class PatrolPath : MonoBehaviour
{
const float waypointGizmoRadius = 0.3f;
private void OnDrawGizmos()
{
for (int i = 0; i < transform.childCount; i++)
{
int j = GetNextIndex(i);
Gizmos.DrawSphere(GetWaypoint(i), waypointGizmoRadius);
Gizmos.DrawLine(GetWaypoint(i), GetWaypoint(j));
}
}
private int GetNextIndex(int i)
{
if (i + 1 == transform.childCount)
{
return 0;
}
return i + 1;
}
private Vector3 GetWaypoint(int i)
{
return transform.GetChild(i).position;
}
}
}
道を歩かせる
1, 下記コードを追加する
AIController
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using RPG.Combat;
using RPG.Core;
using RPG.Movement;
using System;
namespace RPG.Control
{
public class AIController : MonoBehaviour
{
[SerializeField] float chaseDistance = 5f;
[SerializeField] float suspicionTime = 3f;
[SerializeField] PatrolPath patrolPath; //追加
[SerializeField] float waypointTolerance = 1f; //追加
Fighter fighter;
Health health;
Mover mover;
GameObject player;
Vector3 guardPosition;
float timeSinceLastSawPlayer = Mathf.Infinity;
int currentWeypointIndex = 0; //追加
private void Start()
{
fighter = GetComponent<Fighter>();
health = GetComponent<Health>();
mover = GetComponent<Mover>();
player = GameObject.FindWithTag("Player");
guardPosition = transform.position;
}
private void Update()
{
if (health.IsDead()) return;
if (InAttackRangeOfPlayer() && fighter.CanAttack(player))
{
timeSinceLastSawPlayer = 0;
AttackBehaviour();
}
else if (timeSinceLastSawPlayer < suspicionTime)
{
SuspicionBehaviour();
}
else
{
PatrolBehaviour(); //変更
}
timeSinceLastSawPlayer += Time.deltaTime;
}
//変更・追加
private void PatrolBehaviour()
{
Vector3 nextPosition = guardPosition;
if (patrolPath != null)
{
if (AtWaypoint())
{
CycleWaypoint();
}
nextPosition = GetCurrentWaypoint();
}
mover.StartMoveAction(nextPosition);
}
//追加
private bool AtWaypoint()
{
float distanceToWaypoint = Vector3.Distance(transform.position, GetCurrentWaypoint());
return distanceToWaypoint < waypointTolerance;
}
//追加
private void CycleWaypoint()
{
currentWeypointIndex = patrolPath.GetNextIndex(currentWeypointIndex);
}
//追加
private Vector3 GetCurrentWaypoint()
{
return patrolPath.GetWaypoint(currentWeypointIndex);
}
private void SuspicionBehaviour()
{
GetComponent<ActionScheduler>().CancelCurrentAction();
}
private void AttackBehaviour()
{
fighter.Attack(player);
}
private bool InAttackRangeOfPlayer()
{
float distanceToPlayer = Vector3.Distance(player.transform.position, transform.position);
return distanceToPlayer < chaseDistance;
}
private void OnDrawGizmosSelected()
{
Gizmos.color = Color.blue;
Gizmos.DrawWireSphere(transform.position, chaseDistance);
}
}
}
PatrolPath
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace RPG.Control
{
public class PatrolPath : MonoBehaviour
{
const float waypointGizmoRadius = 0.3f;
private void OnDrawGizmos()
{
for (int i = 0; i < transform.childCount; i++)
{
int j = GetNextIndex(i);
Gizmos.DrawSphere(GetWaypoint(i), waypointGizmoRadius);
Gizmos.DrawLine(GetWaypoint(i), GetWaypoint(j));
}
}
public int GetNextIndex(int i) //publicに変更
{
if (i + 1 == transform.childCount)
{
return 0;
}
return i + 1;
}
public Vector3 GetWaypoint(int i) //publicに変更
{
return transform.GetChild(i).position;
}
}
}
2, EnemyのAIController→PatrolPathにPatrolPathを設定する
ポイントで数秒待たせる
1, 下記コードを追加する
AIController
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using RPG.Combat;
using RPG.Core;
using RPG.Movement;
using System;
namespace RPG.Control
{
public class AIController : MonoBehaviour
{
[SerializeField] float chaseDistance = 5f;
[SerializeField] float suspicionTime = 3f;
[SerializeField] PatrolPath patrolPath;
[SerializeField] float waypointTolerance = 1f;
[SerializeField] float waypointDwellTime = 3f; //追加
Fighter fighter;
Health health;
Mover mover;
GameObject player;
Vector3 guardPosition;
float timeSinceLastSawPlayer = Mathf.Infinity;
float timeSinceArrivedAtWaypoint = Mathf.Infinity; //追加
int currentWeypointIndex = 0;
private void Start()
{
fighter = GetComponent<Fighter>();
health = GetComponent<Health>();
mover = GetComponent<Mover>();
player = GameObject.FindWithTag("Player");
guardPosition = transform.position;
}
private void Update()
{
if (health.IsDead()) return;
if (InAttackRangeOfPlayer() && fighter.CanAttack(player))
{
AttackBehaviour();
}
else if (timeSinceLastSawPlayer < suspicionTime)
{
SuspicionBehaviour();
}
else
{
PatrolBehaviour();
}
UpdateTimers(); //追加
}
//追加
private void UpdateTimers()
{
timeSinceLastSawPlayer += Time.deltaTime;
timeSinceArrivedAtWaypoint += Time.deltaTime;
}
private void PatrolBehaviour()
{
Vector3 nextPosition = guardPosition;
if (patrolPath != null)
{
if (AtWaypoint())
{
timeSinceArrivedAtWaypoint = 0; //追加
CycleWaypoint();
}
nextPosition = GetCurrentWaypoint();
}
//追加
if (timeSinceArrivedAtWaypoint > waypointDwellTime)
{
mover.StartMoveAction(nextPosition);
}
}
private bool AtWaypoint()
{
float distanceToWaypoint = Vector3.Distance(transform.position, GetCurrentWaypoint());
return distanceToWaypoint < waypointTolerance;
}
private void CycleWaypoint()
{
currentWeypointIndex = patrolPath.GetNextIndex(currentWeypointIndex);
}
private Vector3 GetCurrentWaypoint()
{
return patrolPath.GetWaypoint(currentWeypointIndex);
}
private void SuspicionBehaviour()
{
GetComponent<ActionScheduler>().CancelCurrentAction();
}
private void AttackBehaviour()
{
timeSinceLastSawPlayer = 0; //移動
fighter.Attack(player);
}
private bool InAttackRangeOfPlayer()
{
float distanceToPlayer = Vector3.Distance(player.transform.position, transform.position);
return distanceToPlayer < chaseDistance;
}
private void OnDrawGizmosSelected()
{
Gizmos.color = Color.blue;
Gizmos.DrawWireSphere(transform.position, chaseDistance);
}
}
}