OldBlueWater/BlueWater/Assets/02.Scripts/Ai/Human/Combat/Pirate/PirateAi.cs

436 lines
15 KiB
C#
Raw Normal View History

2023-09-12 14:46:57 +00:00
using System;
2023-09-18 00:21:55 +00:00
using System.Collections;
2023-09-12 14:46:57 +00:00
using System.Collections.Generic;
2023-09-18 00:21:55 +00:00
using System.Linq;
2023-09-12 14:46:57 +00:00
using Sirenix.OdinInspector;
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 PirateAi : CombatAi, IDamageable
2023-09-12 07:41:11 +00:00
{
2023-09-12 14:46:57 +00:00
#region Properties and variables
2023-09-12 07:41:11 +00:00
2023-09-12 14:46:57 +00:00
[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.red;
2023-09-13 07:05:21 +00:00
[field: SerializeField] public PirateStat PirateStat { get; set; }
2023-09-18 00:21:55 +00:00
private PirateUnit pirateUnit;
2023-09-13 07:05:21 +00:00
private PirateUnit mouseEnterPirateUnit;
private UnitSelection unitSelection;
2023-09-12 14:46:57 +00:00
2023-09-18 00:21:55 +00:00
[SerializeField] protected bool isAttackCoroutine;
[SerializeField] private bool isCommanded;
2023-09-12 14:46:57 +00:00
#endregion
#region Unit Built-in methods
2023-09-18 00:21:55 +00:00
protected virtual void OnDrawGizmosSelected()
{
if (!isDrawGizmosInFieldOfView) return;
if (PirateStat.AttackerType == EAttackerType.OFFENSE)
{
if (!targetTransform) return;
Gizmos.color = Color.red;
Gizmos.DrawLine(transform.position, targetTransform.position);
}
else if (PirateStat.AttackerType == EAttackerType.DEFENSE)
{
if (PirateStat.DefenseType == EDefenseType.DEFENDER)
{
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(defensePos, PirateStat.DefenseRange);
}
Gizmos.color = Color.blue;
Gizmos.DrawWireSphere(transform.position, PirateStat.ViewRange);
if (!targetTransform) return;
Gizmos.color = Color.red;
Gizmos.DrawLine(transform.position, targetTransform.position);
}
}
2023-09-13 07:05:21 +00:00
private void OnMouseEnter()
{
if (!unitSelection || !unitSelection.IsSelectable) return;
mouseEnterPirateUnit = gameObject.GetComponentInParent<PirateUnit>();
if (mouseEnterPirateUnit == unitSelection.SelectedPirateUnit) return;
foreach (var pirateAi in mouseEnterPirateUnit.pirateUnitStat.PirateAiList)
{
pirateAi.MouseEnterHighlight();
}
}
private void OnMouseExit()
{
if (!unitSelection || !unitSelection.IsSelectable ||
!mouseEnterPirateUnit || mouseEnterPirateUnit == unitSelection.SelectedPirateUnit) return;
foreach (var pirateAi in mouseEnterPirateUnit.pirateUnitStat.PirateAiList)
{
pirateAi.ResetHighlight();
}
mouseEnterPirateUnit = null;
}
2023-09-12 14:46:57 +00:00
private void Start()
{
InitStart();
}
#endregion
2023-09-18 00:21:55 +00:00
#region IDamageable interface
public void TakeDamage(float attackerPower, float attackerShieldPenetrationRate, Vector3? attackPos = null)
{
if (attackPos != null && combatAgent.enabled && PirateStat.AttackerType == EAttackerType.DEFENSE && !targetTransform)
{
// BeAttackedMovement((Vector3)attackPos);
}
// 회피 성공 체크
if (Random.Range(0, 100) < PirateStat.AvoidanceRate)
{
// TODO : 회피 처리
return;
}
var finalDamage = 0f;
if (PirateStat.UsingShield)
{
var penetrationChance = attackerShieldPenetrationRate -
(attackerShieldPenetrationRate * PirateStat.PenetrationResistivity * 0.01f);
// 방패를 관통했다면,
if (Random.Range(0, 100) < penetrationChance)
{
finalDamage = attackerPower - PirateStat.Def;
finalDamage = Mathf.Max(finalDamage, 0);
}
else
{
finalDamage = 0f;
}
}
finalDamage = attackerPower - PirateStat.Def;
finalDamage = Mathf.Max(finalDamage, 0);
// 방패 막기 체크
if (finalDamage == 0f)
{
combatAnimator.SetTrigger(ShieldHash);
return;
}
var changeHp = Mathf.Max(PirateStat.CurrentHp - finalDamage, 0);
SetCurrentHp(changeHp);
// 죽었는지 체크
if (changeHp == 0f) return;
combatAnimator.SetTrigger(DamageHash);
}
#endregion
2023-09-12 14:46:57 +00:00
#region Custom methods
2023-09-13 07:05:21 +00:00
protected override void InitComponent()
{
base.InitComponent();
2023-09-18 00:21:55 +00:00
pirateUnit = Utils.GetComponentAndAssert<PirateUnit>(transform.parent);
2023-09-13 07:05:21 +00:00
unitSelection = Utils.GetComponentAndAssert<UnitSelection>(GameObject.Find("UnitManager").transform);
}
2023-09-12 14:46:57 +00:00
protected override void SetLayer()
{
gameObject.layer = LayerMask.NameToLayer("Pirate");
var hitBoxObj = hitBoxCollider.gameObject;
hitBoxObj.layer = LayerMask.NameToLayer("HitBox");
hitBoxObj.tag = "Pirate";
targetLayer = LayerMask.GetMask("Enemy");
2023-09-13 07:05:21 +00:00
if (PirateStat.AttackerType == EAttackerType.OFFENSE)
2023-09-12 14:46:57 +00:00
{
targetLayer |= LayerMask.GetMask("Props");
}
}
protected virtual void InitStart()
{
2023-09-13 07:05:21 +00:00
var pirateViewData = DataManager.Inst.GetPirateViewDictionaryFromKey(PirateStat.ViewIdx);
2023-09-12 14:46:57 +00:00
InitViewModel(pirateViewData);
FindMaterial();
2023-09-18 00:21:55 +00:00
SetBehaviorTree(UnitManager.Inst.PirateBehaviorTree);
2023-09-13 07:05:21 +00:00
SetCurrentHp(PirateStat.MaxHp);
SetMoveSpeed(PirateStat.MoveSpd);
2023-09-12 14:46:57 +00:00
2023-09-13 07:05:21 +00:00
if (PirateStat.AttackerType == EAttackerType.DEFENSE)
2023-09-12 14:46:57 +00:00
{
defensePos = transform.position;
}
2023-09-18 00:21:55 +00:00
2023-09-12 14:46:57 +00:00
}
private void InitViewModel(PirateView pirateView)
{
SetActiveViewModel(backpackContainer, pirateView.Backpack);
SetActiveViewModel(leftWeaponContainer, pirateView.LeftWeapon);
SetActiveViewModel(leftShieldContainer, pirateView.LeftShield);
SetActiveViewModel(headContainer, pirateView.Head);
SetActiveViewModel(rightWeaponContainer, pirateView.RightWeapon);
SetActiveViewModel(bodyContainer, pirateView.Body);
SetActiveViewModel(flagContainer, pirateView.Flag);
}
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);
}
}
2023-09-13 07:05:21 +00:00
2023-09-18 00:21:55 +00:00
public void CommandMoveTarget(Vector3 movePos)
{
StartCoroutine(CommandMoveCoroutine(movePos));
}
private IEnumerator CommandMoveCoroutine(Vector3 movePos)
2023-09-13 07:05:21 +00:00
{
2023-09-18 00:21:55 +00:00
while (isAttacking)
{
yield return null;
}
combatAgent.SetDestination(movePos);
SetIsCommanded(true);
while (combatAgent.pathPending || combatAgent.remainingDistance > combatAgent.stoppingDistance)
{
yield return null;
}
2023-09-13 07:05:21 +00:00
2023-09-18 00:21:55 +00:00
SetIsCommanded(false);
}
public override void FindTarget()
{
switch (PirateStat.AttackerType)
{
case EAttackerType.NONE:
print("PirateStat.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) <= PirateStat.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 (PirateStat.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 (PirateStat.DefenseType)
{
case EDefenseType.NONE:
print("EnemyStat.DefenseType == NONE Error");
break;
case EDefenseType.STRIKER:
FindNearestTargetInRange(transform.position, PirateStat.ViewRange);
break;
case EDefenseType.MIDFIELDER:
FindNearestTargetInRange(transform.position, PirateStat.ViewRange);
break;
case EDefenseType.DEFENDER:
FindNearestTargetInRange(defensePos, PirateStat.DefenseRange);
break;
case EDefenseType.KEEPER:
FindNearestTargetInRange(transform.position, PirateStat.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);
2023-09-13 07:05:21 +00:00
}
2023-09-12 14:46:57 +00:00
2023-09-13 07:05:21 +00:00
private void SetOutlineColor(Color color)
{
foreach (var skin in skinMaterialList)
{
skin.SetColor(OutlineColorHash, color);
}
}
2023-09-18 00:21:55 +00:00
public void SetIsCommanded(bool value)
{
isCommanded = value;
var btIsCommanded = behaviorTree.GetVariable("IsCommanded");
btIsCommanded?.SetValue(value);
}
protected override void SetCurrentHp(float value)
{
PirateStat.CurrentHp = value;
var btCurrentHp = behaviorTree.GetVariable("CurrentHp");
btCurrentHp?.SetValue(value);
}
protected override void RemoveAiListElement()
{
if (pirateUnit.pirateUnitStat.PirateAiList.Contains(this))
{
pirateUnit.pirateUnitStat.PirateAiList.Remove(this);
}
}
2023-09-12 14:46:57 +00:00
2023-09-18 00:21:55 +00:00
public bool GetIsAttackCoroutine() => isAttackCoroutine;
public bool GetIsCommanded() => isCommanded;
2023-09-13 07:05:21 +00:00
public void SetAttackerType(EAttackerType type) => PirateStat.AttackerType = type;
public void SetOffenseType(EOffenseType type) => PirateStat.OffenseType = type;
public void SetDefenseType(EDefenseType type) => PirateStat.DefenseType = type;
public void ResetHighlight() => SetOutlineColor(defaultSkinColor);
public void MouseEnterHighlight() => SetOutlineColor(mouseEnterHighlightSkinColor);
public void SelectedHighlight() => SetOutlineColor(selectedSkinColor);
2023-09-12 14:46:57 +00:00
#endregion
2023-09-12 07:41:11 +00:00
}
}