안녕하세요. 😃
오늘은 지난번에 이어 Enemy 작업을 해주기로 했습니다.
우선 먼저 enemy가 포탑에 근접하였을 때
포탑을 향해 공격을 하며 HpBar를 깎아주는 작업입니다.
지난번 작성한 EnumyMovement > EnemyMovement 스크립트에서 변수를 추가해 주었습니다.
using UnityEngine;
using UnityEngine.AI;
public class EnemyMovement : MonoBehaviour
{
[SerializeField] private EnemyStats enemyStats;
[SerializeField] private Animator enumyAnim;
[SerializeField] private NavMeshAgent agent;
[SerializeField] private string minionTag = "EnemyMinion";
[SerializeField] private string turretTag = "Turret";
[Header("Targeting Settings")]
[SerializeField] private float stopDistance = 2.0f;
[SerializeField] private float aggroRange = 5.0f;
[SerializeField] private float targetSwitchInterval = 2.0f;
[Header("Attack Settings")]
[SerializeField] private float attackCooldown = 1.5f;
private float lastAttackTime = 0f;
private Transform currentTarget;
private float timeSinceLastTargetSwitch = 0.0f;
void Start()
{
agent.stoppingDistance = stopDistance;
UpdateTarget();
}
void Update()
{
timeSinceLastTargetSwitch += Time.deltaTime;
if (timeSinceLastTargetSwitch >= targetSwitchInterval)
{
UpdateTarget();
timeSinceLastTargetSwitch = 0.0f;
}
if (currentTarget != null)
{
agent.SetDestination(currentTarget.position);
float distance = Vector3.Distance(transform.position, currentTarget.position);
// 사거리 안에 들어왔을 때 공격
if (distance <= stopDistance + 0.1f)
{
TryAttack();
}
}
else
{
Debug.LogWarning($"{gameObject.name} has no target to move towards.");
}
}
private void TryAttack()
{
print("222");
if (Time.time >= lastAttackTime + attackCooldown)
{
lastAttackTime = Time.time;
if (currentTarget.tag == minionTag)
{
currentTarget.GetComponent<EnemyStats>().TakeDamage(enemyStats.enemyDamage);
}
else if (currentTarget.tag == turretTag)
{
currentTarget.GetComponent<TurrentStats>().TakeDamage(enemyStats.enemyDamage);
}
enumyAnim.SetTrigger("IsAttack");
}
}
private void UpdateTarget()
{
Transform closestMinion = GetClosestObjectInRadius(GameObject.FindGameObjectsWithTag(minionTag), aggroRange);
if (closestMinion != null)
{
currentTarget = closestMinion;
}
else
{
currentTarget = GetClosestObject(GameObject.FindGameObjectsWithTag(turretTag));
}
}
private Transform GetClosestObject(GameObject[] objects)
{
float closestDistance = Mathf.Infinity;
Transform closestObject = null;
Vector3 currentPosition = transform.position;
foreach (var obj in objects)
{
float distance = Vector3.Distance(currentPosition, obj.transform.position);
if (distance < closestDistance)
{
closestDistance = distance;
closestObject = obj.transform;
}
}
return closestObject;
}
private Transform GetClosestObjectInRadius(GameObject[] objects, float radius)
{
float closestDistance = Mathf.Infinity;
Transform closestObject = null;
Vector3 currentPosition = transform.position;
foreach (var obj in objects)
{
float distance = Vector3.Distance(currentPosition, obj.transform.position);
if (distance < closestDistance && distance <= radius)
{
closestDistance = distance;
closestObject = obj.transform;
}
}
return closestObject;
}
}
1. 추가된 변수 선언부입니다.
[SerializeField] private float attackCooldown = 1.5f; // 공격 간격 (초)
private float lastAttackTime = 0f; // 마지막 공격 시각
attackCooldown : 공격 간격
lastAttackTime : 마지막 공격 시각
2. Update() 함수 내부입니다.
float distance = Vector3.Distance(transform.position, currentTarget.position);
// 사거리 안에 들어왔을 때 공격
if (distance <= stopDistance + 0.1f)
{
TryAttack();
}
현재 포지션과 Target의 위치를 지역변수 distance에 담아 사용하였습니다.
3. TryAttack() 함수입니다.
private void TryAttack()
{
if (Time.time >= lastAttackTime + attackCooldown)
{
lastAttackTime = Time.time;
if (currentTarget.tag == minionTag)
{
currentTarget.GetComponent<EnemyStats>().TakeDamage(enemyStats.enemyDamage);
}
else if (currentTarget.tag == turretTag)
{
currentTarget.GetComponent<TurrentStats>().TakeDamage(enemyStats.enemyDamage);
}
enumyAnim.SetTrigger("IsAttack");
}
}
마지막 공격 시각 + 쿨타임이 흐른 시간보다 크다면 실행되며
공격 대상은 Tag를 지정해 사용했습니다.
(미니언 / 포탑)
+
애니메이션을 추가시켜 주어 시각적인 효과를 더해주었습니다.
TurretStats 스크립트입니다.
using UnityEngine;
public class TurretStats : MonoBehaviour
{
public float damage = 5.0f;
public float turretMaxHp = 200.0f;
public float turretHp = 0f;
[SerializeField] private TurretHpBar turretHpBar;
void Start()
{
turretMaxHp = turretHp;
}
public void TakeDamage(float damage)
{
turretHp -= damage;
turretHpBar.HpVarUpdate(turretHp, turretMaxHp);
if (turretHp <= 0)
{
Destroy(this.gameObject);
}
}
}
TakeDamage() 함수 내에서 현재 포탑의 Hp를 받은 damage 만큼 깎아주며
현재는 포탑의 Hp가 0보다 작거나 같다면 Destroy로 파괴를 시켜주고있습니다.
또한 HpVar가 업데이트되는 부분은 따로 스크립트를 작성하여 관리해 주었습니다.
TurretHpBar 스크립트입니다.
using UnityEngine;
using UnityEngine.UI;
public class TurretHpBar : MonoBehaviour
{
[SerializeField] private Slider turretHpBar;
public void HpVarUpdate(float hp, float maxHp)
{
turretHpBar.value = hp / maxHp;
}
}
포탑에 붙어있는 Slider를 가져와 Value를 조정해 주었습니다.
다음은 Enemy에 더해 적군으로 사용될 Minion 프리팹을 만들어 주었습니다.
기존 private로 사용하던 Tag 변수를 [SerializeField]를 활용해 인스펙터창에 노출시켜 주었습니다.
기존 Enemy Tag
새로 생성한 MinionEnemy Tag
이후 맵에 배치를 해서 각 태그를 맞춰주며 작업을 했습니다.
+
포탑(Turret)의 범위는 HpVar가 있는 Canvas에 Image를 추가시켜 주었습니다.
현재는 enemy들의 공격력은
왼(4) / 오(2)입니다.
원래는 공격력이 같지만, 포탑을 때리는 과정까지 조금 시간이 걸리기에
임시로 준 연출효과입니다.
감사합니다. 😁