using System; using System.Collections; using System.Collections.Generic; using System.Linq; using UnityEngine; using Random = UnityEngine.Random; // ReSharper disable once CheckNamespace namespace BlueWaterProject { public abstract class EnemyAi : CombatAi, IDamageable { #region Properties and variables [field: SerializeField] public EnemyStat EnemyStat { get; set; } protected bool isAttackCoroutine; private bool beAttacked; private EnemyUnit enemyUnit; private int childNum; #endregion #region Unit Built-in methods 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; var startPos = Application.isPlaying ? defensePos : transform.position; Gizmos.DrawWireSphere(startPos, 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); } } private void Start() { InitStart(); } #endregion #region IDamageable interface public void TakeDamage(float attackerPower, Vector3? attackPos = null) { if (attackPos != null && 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, true); // 죽었는지 체크 if (changeHp == 0f) return; combatAnimator.SetTrigger(DamageHash); } #endregion #region Custom methods protected override void InitComponent() { base.InitComponent(); enemyUnit = Utils.GetComponentAndAssert(transform.parent); } 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"); if (EnemyStat.AttackerType == EAttackerType.OFFENSE) { targetLayer |= LayerMask.GetMask("Props"); } } #if UNITY_EDITOR public virtual void InitStartInEditor() { var enemyViewData = DataManager.Inst.GetEnemyViewSoFromKey(EnemyStat.ViewIdx); InitComponent(); SetLayer(); InitViewModel(enemyViewData); } #endif protected virtual void InitStart() { var enemyViewData = DataManager.Inst.GetEnemyViewDictionaryFromKey(EnemyStat.ViewIdx); InitViewModel(enemyViewData); SetBehaviorTree(UnitManager.Inst.EnemyBehaviorTree); SetCurrentHp(EnemyStat.MaxHp, true); SetMoveSpeed(EnemyStat.MoveSpd); if (EnemyStat.AttackerType == EAttackerType.DEFENSE) { SetDefensePos(transform.position, true); childNum = transform.GetSiblingIndex(); enemyUnit.SetDefensePos(defensePos, childNum); } } 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); } 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.ExceptHouseList.Count > 0) { FindNearestTargetInList(attackingIslandInfo.ExceptHouseList); } 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.ExceptHouseList.Count > 0) { FindNearestTargetInList(attackingIslandInfo.ExceptHouseList); } break; default: throw new ArgumentOutOfRangeException(); } } protected virtual void FindNearestTargetInList(List 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, true); } 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, true); 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, true); } 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) { combatAgent.stoppingDistance = GlobalValue.MINIMUM_STOP_DISTANCE; combatAgent.SetDestination(defensePos); return; } break; case EDefenseType.KEEPER: return; default: throw new ArgumentOutOfRangeException(); } if (Vector3.Distance(combatAgent.destination, targetPos) < 0.1f) return; combatAgent.stoppingDistance = GlobalValue.MAXIMUM_STOP_DISTANCE; combatAgent.SetDestination(targetPos); } public void ReturnDefensePos(Vector3 targetPos) { if (Vector3.Distance(combatAgent.destination, targetPos) < 0.1f) return; combatAgent.stoppingDistance = GlobalValue.MINIMUM_STOP_DISTANCE; combatAgent.SetDestination(targetPos); } protected override void SetCurrentHp(float value, bool useBehaviorTreeVariable = false) { EnemyStat.CurrentHp = value; if (!useBehaviorTreeVariable) return; Utils.SetBehaviorVariable(behaviorTree, "CurrentHp", value); } protected override void RemoveAiListElement() { if (enemyUnit.enemyUnitStat.EnemyAiList.Contains(this)) { enemyUnit.enemyUnitStat.EnemyAiList.Remove(this); } enemyUnit.ResetDefensePos(); } 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.SetBeAttacked(true, true); item.MoveTarget(attackPos, GlobalValue.MAXIMUM_STOP_DISTANCE); } } public void SetDefensePos(Vector3 value, bool useBehaviorTreeVariable = false) { defensePos = value; if (!useBehaviorTreeVariable) return; Utils.SetBehaviorVariable(behaviorTree, "DefensePos", value); } public void SetBeAttacked(bool value, bool useBehaviorTreeVariable = false) { beAttacked = value; if (!useBehaviorTreeVariable) return; Utils.SetBehaviorVariable(behaviorTree, "BeAttacked", value); } public bool GetIsAttackCoroutine() => isAttackCoroutine; public void SetAttackerType(EAttackerType type) => EnemyStat.AttackerType = type; public void SetOffenseType(EOffenseType type) => EnemyStat.OffenseType = type; public void SetDefenseType(EDefenseType type) => EnemyStat.DefenseType = type; #endregion } }