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
|
|
|
}
|
|
|
|
}
|