OldBlueWater/BlueWater/Assets/02.Scripts/Ai/Human/Combat/Enemy/EnemyAi.cs

401 lines
14 KiB
C#
Raw Normal View History

2023-09-18 00:21:55 +00:00
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
2023-09-12 07:41:11 +00:00
using UnityEngine;
2023-09-18 00:21:55 +00:00
using Random = UnityEngine.Random;
2023-09-12 07:41:11 +00:00
// ReSharper disable once CheckNamespace
namespace BlueWaterProject
{
2023-09-18 00:21:55 +00:00
public abstract class EnemyAi : CombatAi, IDamageable
2023-09-12 07:41:11 +00:00
{
2023-09-12 14:46:57 +00:00
#region Properties and variables
2023-09-13 07:05:21 +00:00
[field: SerializeField] public EnemyStat EnemyStat { get; set; }
2023-09-12 14:46:57 +00:00
2023-09-18 00:21:55 +00:00
protected bool isAttackCoroutine;
private EnemyUnit enemyUnit;
private int childNum;
2023-09-12 14:46:57 +00:00
#endregion
2023-09-12 07:41:11 +00:00
2023-09-12 14:46:57 +00:00
#region Unit Built-in methods
2023-09-18 00:21:55 +00:00
protected virtual void OnDrawGizmosSelected()
{
if (!isDrawGizmosInFieldOfView) return;
if (EnemyStat.AttackerType == EAttackerType.OFFENSE)
{
if (!targetTransform) return;
Gizmos.color = Color.red;
Gizmos.DrawLine(transform.position, targetTransform.position);
}
else if (EnemyStat.AttackerType == EAttackerType.DEFENSE)
{
if (EnemyStat.DefenseType == EDefenseType.DEFENDER)
{
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(defensePos, EnemyStat.DefenseRange);
}
Gizmos.color = Color.blue;
Gizmos.DrawWireSphere(transform.position, EnemyStat.ViewRange);
if (!targetTransform) return;
Gizmos.color = Color.red;
Gizmos.DrawLine(transform.position, targetTransform.position);
}
}
2023-09-12 14:46:57 +00:00
private void Start()
{
InitStart();
}
2023-09-18 00:21:55 +00:00
#endregion
#region IDamageable interface
public void TakeDamage(float attackerPower, float attackerShieldPenetrationRate, Vector3? attackPos = null)
{
if (attackPos != null && combatAgent.enabled && EnemyStat.AttackerType == EAttackerType.DEFENSE && !targetTransform)
{
BeAttackedMovement((Vector3)attackPos);
}
// 회피 성공 체크
if (Random.Range(0, 100) < EnemyStat.AvoidanceRate)
{
// TODO : 회피 처리
return;
}
var finalDamage = 0f;
if (EnemyStat.UsingShield)
{
var penetrationChance = attackerShieldPenetrationRate -
(attackerShieldPenetrationRate * EnemyStat.PenetrationResistivity * 0.01f);
// 방패를 관통했다면,
if (Random.Range(0, 100) < penetrationChance)
{
finalDamage = attackerPower - EnemyStat.Def;
finalDamage = Mathf.Max(finalDamage, 0);
}
else
{
finalDamage = 0f;
}
}
finalDamage = attackerPower - EnemyStat.Def;
finalDamage = Mathf.Max(finalDamage, 0);
// 방패 막기 체크
if (finalDamage == 0f)
{
combatAnimator.SetTrigger(ShieldHash);
return;
}
var changeHp = Mathf.Max(EnemyStat.CurrentHp - finalDamage, 0);
SetCurrentHp(changeHp);
// 죽었는지 체크
if (changeHp == 0f) return;
combatAnimator.SetTrigger(DamageHash);
}
2023-09-12 14:46:57 +00:00
#endregion
#region Custom methods
2023-09-18 00:21:55 +00:00
protected override void InitComponent()
{
base.InitComponent();
enemyUnit = Utils.GetComponentAndAssert<EnemyUnit>(transform.parent);
}
2023-09-12 14:46:57 +00:00
protected override void SetLayer()
{
gameObject.layer = LayerMask.NameToLayer("Enemy");
var hitBoxObj = hitBoxCollider.gameObject;
hitBoxObj.layer = LayerMask.NameToLayer("HitBox");
hitBoxObj.tag = "Enemy";
targetLayer = LayerMask.GetMask("Player") | LayerMask.GetMask("Pirate");
2023-09-13 07:05:21 +00:00
if (EnemyStat.AttackerType == EAttackerType.OFFENSE)
2023-09-12 14:46:57 +00:00
{
targetLayer |= LayerMask.GetMask("Props");
}
}
2023-09-13 07:05:21 +00:00
#if UNITY_EDITOR
public virtual void InitStartInEditor()
{
var enemyViewData = DataManager.Inst.GetEnemyViewSoFromKey(EnemyStat.ViewIdx);
InitComponent();
SetLayer();
InitViewModel(enemyViewData);
}
#endif
2023-09-12 14:46:57 +00:00
protected virtual void InitStart()
{
2023-09-13 07:05:21 +00:00
var enemyViewData = DataManager.Inst.GetEnemyViewDictionaryFromKey(EnemyStat.ViewIdx);
2023-09-12 14:46:57 +00:00
InitViewModel(enemyViewData);
2023-09-18 00:21:55 +00:00
SetBehaviorTree(UnitManager.Inst.EnemyBehaviorTree);
2023-09-13 07:05:21 +00:00
SetCurrentHp(EnemyStat.MaxHp);
SetMoveSpeed(EnemyStat.MoveSpd);
2023-09-18 00:21:55 +00:00
childNum = enemyUnit.transform.GetSiblingIndex();
2023-09-13 07:05:21 +00:00
if (EnemyStat.AttackerType == EAttackerType.DEFENSE)
2023-09-12 14:46:57 +00:00
{
2023-09-18 00:21:55 +00:00
SetDefensePos(transform.position);
enemyUnit.SetDefensePos(defensePos, childNum);
2023-09-12 14:46:57 +00:00
}
}
private void InitViewModel(EnemyView enemyView)
{
SetActiveViewModel(backpackContainer, enemyView.Backpack);
SetActiveViewModel(leftWeaponContainer, enemyView.LeftWeapon);
SetActiveViewModel(leftShieldContainer, enemyView.LeftShield);
SetActiveViewModel(headContainer, enemyView.Head);
SetActiveViewModel(rightWeaponContainer, enemyView.RightWeapon);
SetActiveViewModel(bodyContainer, enemyView.Body);
SetActiveViewModel(flagContainer, enemyView.Flag);
}
2023-09-18 00:21:55 +00:00
public override void FindTarget()
{
switch (EnemyStat.AttackerType)
{
case EAttackerType.NONE:
print("EnemyStat.AttackerType == NONE Error");
break;
case EAttackerType.OFFENSE:
FindTargetInOffense();
break;
case EAttackerType.DEFENSE:
FindTargetInDefense();
break;
default:
throw new ArgumentOutOfRangeException();
}
}
public override bool CanAttack()
{
if (!targetTransform) return false;
var attackInRange = Vector3.Distance(transform.position, targetTransform.position) <= EnemyStat.AtkRange;
return attackInRange;
}
public override void Attack()
{
isAttackCoroutine = true;
StartCoroutine(nameof(AttackAnimation));
}
protected abstract IEnumerator AttackAnimation();
private void FindTargetInOffense()
{
if (!attackingIslandInfo)
{
print("attackingIslandInfo == null error");
return;
}
switch (EnemyStat.OffenseType)
{
case EOffenseType.NONE:
print("AiStat.OffenseType == NONE Error");
break;
case EOffenseType.NORMAL:
if (attackingIslandInfo.EnemyList.Count > 0)
{
FindNearestTargetInList(attackingIslandInfo.EnemyList);
}
else if (attackingIslandInfo.HouseList.Count > 0)
{
FindNearestTargetInList(attackingIslandInfo.HouseList);
}
break;
case EOffenseType.ONLY_HOUSE:
if (attackingIslandInfo.HouseList.Count > 0)
{
FindNearestTargetInList(attackingIslandInfo.HouseList);
}
else if (attackingIslandInfo.EnemyList.Count > 0)
{
FindNearestTargetInList(attackingIslandInfo.EnemyList);
}
break;
default:
throw new ArgumentOutOfRangeException();
}
}
protected virtual void FindNearestTargetInList(List<Transform> targetList)
{
if (targetList.Count <= 0) return;
var nearestTarget = targetList.OrderBy(t => t ?
Vector3.Distance(transform.position, t.position) : float.MaxValue).FirstOrDefault();
if (nearestTarget == null) return;
SetTargetTransform(nearestTarget);
}
private void FindTargetInDefense()
{
switch (EnemyStat.DefenseType)
{
case EDefenseType.NONE:
print("EnemyStat.DefenseType == NONE Error");
break;
case EDefenseType.STRIKER:
FindNearestTargetInRange(transform.position, EnemyStat.ViewRange);
break;
case EDefenseType.MIDFIELDER:
FindNearestTargetInRange(transform.position, EnemyStat.ViewRange);
break;
case EDefenseType.DEFENDER:
FindNearestTargetInRange(defensePos, EnemyStat.DefenseRange);
break;
case EDefenseType.KEEPER:
FindNearestTargetInRange(transform.position, EnemyStat.ViewRange);
break;
default:
throw new ArgumentOutOfRangeException();
}
}
protected virtual void FindNearestTargetInRange(Vector3 centerPos, float range)
{
Array.Clear(colliderWithinRange, 0, TARGET_MAX_SIZE);
var maxColliderCount = Physics.OverlapSphereNonAlloc(centerPos, range, colliderWithinRange,
targetLayer, QueryTriggerInteraction.Collide);
if (maxColliderCount <= 0)
{
SetTargetTransform(null);
return;
}
var nearestDistance = Mathf.Infinity;
Transform nearestTargetTransform = null;
for (var i = 0; i < maxColliderCount; i++)
{
var distanceToTarget = Vector3.Distance(transform.position, colliderWithinRange[i].transform.position);
if (distanceToTarget >= nearestDistance) continue;
nearestDistance = distanceToTarget;
nearestTargetTransform = colliderWithinRange[i].transform;
}
SetTargetTransform(nearestTargetTransform);
}
public void MoveTargetInDefense(Vector3 targetPos)
{
switch (EnemyStat.DefenseType)
{
case EDefenseType.NONE:
print("EnemyStat.DefenseType == NONE error");
break;
case EDefenseType.STRIKER:
case EDefenseType.MIDFIELDER:
break;
case EDefenseType.DEFENDER:
if (Vector3.Distance(targetPos, defensePos) > EnemyStat.DefenseRange) return;
break;
case EDefenseType.KEEPER:
return;
default:
throw new ArgumentOutOfRangeException();
}
if (Vector3.Distance(combatAgent.destination, targetPos) < 0.1f) return;
combatAgent.SetDestination(targetPos);
}
protected override void SetCurrentHp(float value)
{
EnemyStat.CurrentHp = value;
var btCurrentHp = behaviorTree.GetVariable("CurrentHp");
btCurrentHp?.SetValue(value);
}
protected override void RemoveAiListElement()
{
if (enemyUnit.enemyUnitStat.EnemyAiList.Contains(this))
{
enemyUnit.enemyUnitStat.EnemyAiList.Remove(this);
}
childNum = enemyUnit.transform.GetSiblingIndex();
SetDefensePos(enemyUnit.GetDefensePos(childNum));
}
private void BeAttackedMovement(Vector3 attackPos)
{
switch (EnemyStat.DefenseType)
{
case EDefenseType.NONE:
print("EnemyStat.DefenseType == NONE Error");
break;
case EDefenseType.STRIKER:
case EDefenseType.MIDFIELDER:
break;
case EDefenseType.DEFENDER:
if (Vector3.Distance(defensePos, attackPos) > EnemyStat.DefenseRange) return;
break;
case EDefenseType.KEEPER:
return;
default:
throw new ArgumentOutOfRangeException();
}
foreach (var item in enemyUnit.enemyUnitStat.EnemyAiList)
{
if (item.GetTargetTransform()) continue;
item.MoveTarget(attackPos);
}
}
public void SetDefensePos(Vector3 value)
{
defensePos = value;
var btDefensePos = behaviorTree.GetVariable("DefensePos");
btDefensePos?.SetValue(value);
}
public bool GetIsAttackCoroutine() => isAttackCoroutine;
2023-09-13 07:05:21 +00:00
public void SetAttackerType(EAttackerType type) => EnemyStat.AttackerType = type;
public void SetOffenseType(EOffenseType type) => EnemyStat.OffenseType = type;
public void SetDefenseType(EDefenseType type) => EnemyStat.DefenseType = type;
2023-09-12 14:46:57 +00:00
#endregion
2023-09-12 07:41:11 +00:00
}
}