OldBlueWater/BlueWater/Assets/02.Scripts/Ai/AiController.cs

382 lines
12 KiB
C#
Raw Normal View History

using System;
using System.Collections;
using System.Collections.Generic;
using Sirenix.OdinInspector;
using UnityEngine;
2023-08-08 07:53:35 +00:00
using UnityEngine.AI;
using UnityEngine.Serialization;
using Random = UnityEngine.Random;
// ReSharper disable once CheckNamespace
namespace BlueWaterProject
{
public enum AttackerType
{
NONE,
PLAYER,
PIRATE,
ENEMY
}
[Serializable]
public abstract class AiController : MonoBehaviour, IDamageable, IFieldOfView, IAiMover
{
#region Property and variable
[Title("Skin")]
[Tooltip("SkinnedMeshRenderer, MeshRenderer의 Material을 모두 담고 있는 리스트")]
[SerializeField] protected List<Material> skinMaterialList = new(10);
[Tooltip("캐릭터 외곽선의 기본 색상")]
[SerializeField] protected Color defaultSkinColor = Color.black;
[Tooltip("캐릭터에 마우스 커서가 올라가 있을 때 색상")]
[SerializeField] protected Color mouseEnterHighlightSkinColor = Color.white;
[Tooltip("캐릭터가 선택되었을 때 색상")]
[SerializeField] protected Color selectedSkinColor = Color.blue;
2023-08-08 07:53:35 +00:00
protected bool isAttacking;
protected AttackerType attackerType;
2023-08-08 07:53:35 +00:00
protected Animator aiAnimator;
2023-08-08 07:53:35 +00:00
protected NavMeshAgent navMeshAgent;
private UnitController mouseEnterUnitController;
private UnitSelection unitSelection;
private static readonly int SpeedHash = Animator.StringToHash("Speed");
protected static readonly int AttackHash = Animator.StringToHash("Attack");
private static readonly int DamageHash = Animator.StringToHash("TakeDamage");
private static readonly int DeathTypeHash = Animator.StringToHash("DeathType");
private static readonly int DeathHash = Animator.StringToHash("Death");
private static readonly int OutlineColorHash = Shader.PropertyToID("_OutlineColor");
private static readonly WaitForSeconds FindTargetWaitTime = new(0.5f);
#endregion
#region abstract function
protected abstract void Attack();
#endregion
#region Unity built-in function
private void OnDrawGizmosSelected()
{
DrawGizmosInFieldOfView();
}
protected virtual void Awake()
{
FindMaterial();
aiAnimator = Utils.GetComponentAndAssert<Animator>(transform);
2023-08-08 07:53:35 +00:00
navMeshAgent = Utils.GetComponentAndAssert<NavMeshAgent>(transform);
unitSelection = FindObjectOfType<UnitSelection>();
if (gameObject.layer == LayerMask.NameToLayer("Player"))
{
attackerType = AttackerType.PLAYER;
}
else if (gameObject.layer == LayerMask.NameToLayer("Pirate"))
{
attackerType = AttackerType.PIRATE;
}
else if (gameObject.layer == LayerMask.NameToLayer("Enemy"))
{
attackerType = AttackerType.ENEMY;
}
}
private void Start()
{
SetCurrentHp(AiStat.maxHp);
navMeshAgent.speed = AiStat.moveSpd;
StartCoroutine(nameof(FindTarget));
Attack();
}
private void FixedUpdate()
2023-08-08 07:53:35 +00:00
{
UpdateLookAtTarget();
UpdateMovement();
}
private void OnMouseEnter()
{
mouseEnterUnitController = gameObject.GetComponentInParent<UnitController>();
if (mouseEnterUnitController == unitSelection.SelectedUnitController) return;
foreach (var soldier in mouseEnterUnitController.unit.soldierList)
{
soldier.MouseEnterHighlight();
}
}
private void OnMouseExit()
{
if (!mouseEnterUnitController || mouseEnterUnitController == unitSelection.SelectedUnitController) return;
foreach (var soldier in mouseEnterUnitController.unit.soldierList)
{
soldier.ResetHighlight();
}
mouseEnterUnitController = null;
2023-08-08 07:53:35 +00:00
}
#endregion
#region interface property and function
#region IAiStat
[field: Space(10f)]
[field: Title("AiStat")]
[field: SerializeField] public AiStat AiStat { get; set; } = new();
public float GetCurrentHp() => AiStat.currentHp;
public void TakeDamage(AiStat attacker, AiStat defender)
{
// 회피 성공 체크
if (Random.Range(0, 100) < defender.avoidanceRate)
{
// TODO : 회피 처리
return;
}
var finalDamage = Utils.CalcDamage(attacker, defender);
// 방패 막기 체크
if (finalDamage == 0f)
{
// TODO : 방패로 막힘 처리(애니메이션 등)
return;
}
var changeHp = Mathf.Max(defender.currentHp - finalDamage, 0);
SetCurrentHp(changeHp);
// 죽었는지 체크
if (changeHp == 0f)
{
2023-08-08 07:53:35 +00:00
var randomValue = Random.Range(0, 2);
aiAnimator.SetInteger(DeathTypeHash, randomValue);
// TODO : 죽었을 때 처리(죽는 애니메이션 이후 사라지는 효과 등)
aiAnimator.SetTrigger(DeathHash);
2023-08-08 07:53:35 +00:00
Invoke(nameof(DestroyObject), 5f);
return;
}
aiAnimator.SetTrigger(DamageHash);
}
#endregion
#region IFieldOfView
[field: Space(10f)]
[field: Title("FieldOfView")]
[field: SerializeField] public bool IsDrawGizmosInFieldOfView { get; set; } = true;
[field: SerializeField] public LayerMask TargetLayer { get; set; }
[field: SerializeField] public float ViewRadius { get; set; }
[field: SerializeField] public Collider[] ColliderWithinRange { get; set; } = new Collider[TARGET_MAX_SIZE];
[field: SerializeField] public List<TargetInfo> TargetInfoList { get; set; } = new(TARGET_MAX_SIZE);
[field: SerializeField] public IAiStat IaiStat { get; set; }
[field: SerializeField] public TargetInfo TargetInfo { get; set; } = new();
private const int TARGET_MAX_SIZE = 30;
#if UNITY_EDITOR
public void DrawGizmosInFieldOfView()
{
if (!IsDrawGizmosInFieldOfView) return;
var myPos = transform.position;
Gizmos.color = Color.green;
Gizmos.DrawWireSphere(myPos, ViewRadius);
if (TargetInfo.transform == null) return;
Debug.DrawLine(myPos, TargetInfo.transform.position, Color.red);
}
#endif
public IEnumerator FindTarget()
{
while (true)
{
Array.Clear(ColliderWithinRange, 0, TARGET_MAX_SIZE);
TargetInfoList.Clear();
var myPos = transform.position;
var maxColliderCount = Physics.OverlapSphereNonAlloc(myPos, ViewRadius, ColliderWithinRange,
TargetLayer, QueryTriggerInteraction.Collide);
if (maxColliderCount <= 0)
{
TargetInfo.DefaultSetting();
yield return FindTargetWaitTime;
continue;
}
for (var i = 0; i < maxColliderCount; i++)
{
IaiStat = ColliderWithinRange[i].GetComponent<IAiStat>();
if (IaiStat != null)
{
TargetInfoList.Add(new TargetInfo(ColliderWithinRange[i].transform, ColliderWithinRange[i], IaiStat));
}
}
if (TargetInfoList.Count <= 0)
{
TargetInfo.DefaultSetting();
yield return FindTargetWaitTime;
continue;
}
int nearestIndex;
float nearestDistance;
if (TargetInfoList[0].iAiStat.GetCurrentHp() <= 0)
{
nearestIndex = 0;
nearestDistance = float.PositiveInfinity;
}
else
{
nearestIndex = 0;
nearestDistance = Vector3.Distance(TargetInfoList[0].transform.position, myPos);
}
for (var i = 1; i < TargetInfoList.Count; i++)
{
var distance = Vector3.Distance(TargetInfoList[i].transform.position, myPos);
if (nearestDistance < distance || TargetInfoList[i].iAiStat.GetCurrentHp() <= 0) continue;
nearestIndex = i;
nearestDistance = distance;
}
if (TargetInfoList[nearestIndex].transform)
{
TargetInfo.SetTargetInfo(TargetInfoList[nearestIndex].transform, TargetInfoList[nearestIndex].collider, TargetInfoList[nearestIndex].iAiStat);
}
else
{
TargetInfo.DefaultSetting();
}
yield return FindTargetWaitTime;
}
}
public void UpdateLookAtTarget()
{
if (TargetInfo.transform)
{
navMeshAgent.updateRotation = false;
var targetPos = TargetInfo.transform.position;
targetPos.y = transform.position.y;
transform.LookAt(targetPos);
}
else
{
navMeshAgent.updateRotation = true;
}
}
#endregion
#region IAiMover
[field: Space(10f)]
[field: Title("Mover")]
[field: SerializeField] public MoveType MoveType { get; set; }
[field: SerializeField] public bool IsCommanded { get; set; }
public void UpdateMovement()
{
aiAnimator.SetFloat(AiController.SpeedHash, navMeshAgent.velocity.normalized.magnitude);
if (IsCommanded || isAttacking)
{
}
else
{
if (!TargetInfo.transform || MoveType is MoveType.NONE or MoveType.FIXED) return;
navMeshAgent.SetDestination(TargetInfo.transform.position);
}
}
public void MoveTarget(Vector3 targetPos)
{
IsCommanded = true;
navMeshAgent.SetDestination(targetPos);
}
#endregion
#endregion
#region Custom function
private void FindMaterial()
{
var skinnedMeshRenderers = GetComponentsInChildren<SkinnedMeshRenderer>();
var meshRenderers = GetComponentsInChildren<MeshRenderer>();
foreach (var skin in skinnedMeshRenderers)
{
if (!skin.gameObject.activeSelf) continue;
skinMaterialList.Add(skin.material);
}
foreach (var skin in meshRenderers)
{
if (!skin.gameObject.activeSelf) continue;
skinMaterialList.Add(skin.material);
}
}
private void SetOutlineColor(Color color)
{
foreach (var skin in skinMaterialList)
{
skin.SetColor(OutlineColorHash, color);
}
}
public void ResetHighlight() => SetOutlineColor(Color.black);
public void MouseEnterHighlight() => SetOutlineColor(Color.white);
public void SelectedHighlight() => SetOutlineColor(Color.blue);
private void DestroyObject() => Destroy(gameObject);
2023-08-08 07:53:35 +00:00
public bool GetIsAttacking() => isAttacking;
public NavMeshAgent GetNavMeshAgent() => navMeshAgent;
public Animator GetAnimator() => aiAnimator;
public void SetCurrentHp(float value) => AiStat.currentHp = value;
#endregion
}
}