Hi,
I’ve been making a 2D top-down game where enemies by default are targeting the player, moving towards him and finally attacking him, but if any of the buildings created by the player show within the enemy’s field of detection, they will instead start moving towards the buildings and attack them. Once the building is destroyed, the enemy goes back to moving towards the player. If the enemy gets shot by the player, he gets to focus on the player until one of them dies.
I’ve had a hard time finding the best way of detecting collisions between enemies, buildings and player while making sure that the enemy will always stop before whatever he’s attacking, independent of its size.
Finally I decided that enemies have to have kinematic rigidbodies, to prevent getting pushed by the player, buildings have to be static, so that the player never push them, and for the time being it makes no difference whether the player is dynamic or static.
I’ve finally come up with a system that works, but it looks hideous, so I would appreciate any opinion on that and perhaps a suggestion if that can be achieved more elegantly.
public class Enemy : MonoBehaviour
{
private Transform _currentTarget;
[Header("Movement parameters")]
[SerializeField] private float _moveSpeed;
[SerializeField] private float _rotationSpeed;
[Header("Target Searching")]
[SerializeField] private float _tagetSearchRadius;
[SerializeField] private LayerMask _whatIsTarget;
[SerializeField] private float _stoppingDistanceRadius;
private Health _health;
private bool _isAggroActive;
private void Awake()
{
_health = GetComponent<Health>();
}
private void Start()
{
_currentTarget = PlayerMover.Instance.transform;
_health.OnDied += health_OnDied;
_health.OnHit += health_OnHit;
}
private void health_OnHit(object sender, EventArgs e)
{
ChangeCurrentTarget(PlayerMover.Instance.transform);
SetAggroActive(true);
}
private void health_OnDied(object sender, EventArgs e)
{
WaveSpawnerVer2.Instance.DecreaseAliveSpawnedEnemies();
Destroy(gameObject);
}
private void Update()
{
ResetCurrentTarget();
Debug.DrawLine(transform.position, _currentTarget.position, Color.red);
MoveToTarget();
if (_isAggroActive) { return; }
LookForCloserTarget();
}
private void ResetCurrentTarget()
{
if (_currentTarget == null)
{
ChangeCurrentTarget(PlayerMover.Instance.transform);
}
}
private void MoveToTarget()
{
if (HasReachedFightingArea()) { return; }
Vector3 direction = (_currentTarget.position - transform.position).normalized;
transform.position += direction * Time.deltaTime * _moveSpeed;
}
private bool HasReachedFightingArea()
{
Collider2D[] hitColliders = Physics2D.OverlapCircleAll(transform.position, _stoppingDistanceRadius, _whatIsTarget);
foreach (Collider2D collider in hitColliders)
{
if (collider.transform == _currentTarget)
{
return true;
}
}
return false;
}
private void LookForCloserTarget()
{
Collider2D[] hitColliders = Physics2D.OverlapCircleAll(transform.position, _tagetSearchRadius, _whatIsTarget);
if (hitColliders.Length > 0)
{
foreach (Collider2D collider in hitColliders)
{
if ((collider.transform.position - transform.position).magnitude < (_currentTarget.transform.position - transform.position).magnitude)
{
ChangeCurrentTarget(collider.transform);
Debug.Log("Found new target");
}
}
}
}
private void ChangeCurrentTarget(Transform newTarget)
{
_currentTarget = newTarget;
}
public void SetAggroActive(bool isActive)
{
_isAggroActive = isActive;
}
public Transform GetCurrentTarget()
{
return _currentTarget;
}
private void OnDrawGizmos()
{
Gizmos.color = Color.blue;
Gizmos.DrawWireSphere(transform.position, _tagetSearchRadius);
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(transform.position, _stoppingDistanceRadius);
}
}
public class EnemyAttacker : MonoBehaviour
{
[SerializeField] private float _fightingAreaRadius;
[SerializeField] private LayerMask _whatIsTarget;
[SerializeField] private float _timeSinceLastAttack;
[SerializeField] private float _attackTime;
[SerializeField] private int _damagePoints;
private Enemy _enemy;
private void Awake()
{
_enemy = GetComponent<Enemy>();
}
private void Update()
{
DetectTargetInFightingArea();
}
private void DetectTargetInFightingArea()
{
Collider2D[] hitColliders = Physics2D.OverlapCircleAll(transform.position, _fightingAreaRadius, _whatIsTarget);
foreach (Collider2D collider in hitColliders)
{
if (collider.transform == _enemy.GetCurrentTarget())
{
AttackTarget(collider.transform);
}
}
}
private void AttackTarget(Transform targetTransform)
{
Health tagetHealth = targetTransform.GetComponent<Health>();
if (_timeSinceLastAttack > _attackTime)
{
tagetHealth.TakeDamage(_damagePoints);
_timeSinceLastAttack = 0;
}
_timeSinceLastAttack += Time.deltaTime;
}
private void OnDrawGizmos()
{
Gizmos.DrawWireSphere(transform.position, _fightingAreaRadius);
}
}
I’m afraid that even though it works, I might be missing something, which causes it to be badly structured.
Thanks